Skip to content
RU
Scale
Density
Theme
Accent

Layout

Rail

Vertical navigation in glyphs alone, pushed to the edge of the window. The sections are recognised by the shape of the glyph rather than by a word, so there is no label in a rail — it moves into the tooltip and into the accessible name.

The component
Markup
<nav class="inst-rail" aria-label="Sections">
  <a class="inst-rail-item" href="#rail" aria-current="page" aria-label="Files">
    <svg class="inst-icon" aria-hidden="true"><use href="#i-list"/></svg>
  </a>
  <a class="inst-rail-item" href="#rail" aria-label="Projects">
    <svg class="inst-icon" aria-hidden="true"><use href="#i-grid"/></svg>
  </a>
  <a class="inst-rail-item" href="#rail" aria-label="Runs">
    <svg class="inst-icon" aria-hidden="true"><use href="#i-refresh"/></svg>
    <span class="inst-badge inst-badge--count inst-rail-count">11</span>
  </a>
  <a class="inst-rail-item inst-rail-spacer" href="#rail" aria-label="Parameters">
    <svg class="inst-icon" aria-hidden="true"><use href="#i-settings"/></svg>
  </a>
</nav>

Contract

A rail is a <nav> with a name of its own and a row of links inside. The glyph has to carry an aria-hidden, and the name of the section has to live in the aria-label of the link itself.

What Required Why
An aria-label on every item yes A button with no text has no accessible name. Without it a rail reads as a row of empty buttons
An aria-hidden on the glyph yes Otherwise a screen reader reads out the contents of the SVG over the name
An aria-current="page" on the current one yes The fill reports the choice to the eye, the attribute to everything else
A tooltip on hover no, but wanted The shape of a glyph is not recognised by everybody and not at once
An <a> for going to an address yes A section has an address, and the middle click has to work

Accessibility

The accessible name From the aria-label alone. A glyph is a picture and there is no text in it
The tap target --control-h-lg, that is 38px at the ordinary density and 32 at the compact one — above the norm of 24 in both
The current section An aria-current="page" rather than colour alone: colour as the only carrier is forbidden
The contrast of the glyph --text-secondary at rest, --accent-text on the current one — both above 4.5:1 in five themes
The landmark A <nav> with an aria-label, otherwise the page has two nameless navigation landmarks

Anatomy

The part What it does
.inst-rail The column. The width is not set as a number: it is made of the padding and the button, so it travels with density
.inst-rail-item A button of --control-h-lg. Larger than --tap-min deliberately: a 24px glyph in a 24px target would fill it entire
.inst-rail-spacer It pushes the rest to the bottom. The account and the parameters stand at the bottom rather than being set off by a margin
.inst-rail-count The corner of the glyph, on the outside. From the inside a counter would cover the shape the section is recognised by

The current section carries two carriers: the fill and the mark at the edge. The fill alone is not enough — on the muted surface of a rail it barely differs from a hover; the mark is seen by peripheral vision and does not argue with the glyph.

API

Classes
NameWhat it does
class
inst-railA column of glyphs. The width is intrinsic: the padding plus the button
inst-rail-itemA section. A link or a button. An aria-label is required
inst-rail-spacerA break: everything after it is pushed to the bottom
inst-rail-countThe room for a counter in the corner of a glyph. Put on .inst-badge--count
Attributes
NameValueWhat it does
attribute
aria-currentpageThe current section: a fill plus a mark at the edge
aria-label—Required. A glyph with no name has no accessible name at all
Tokens
NameValueWhere from
variable
--icon-sizeSet on the item and inherited by the glyph. Through a variable, because the layout lies in an earlier layer than the glyph--size-icon-lgthe application sets it
token
--size-icon-lgA glyph with no label. Larger than usual deliberately24pxbase +4: scale
--control-h-lgThe button. Larger than --tap-min, otherwise the glyph fills it entire38pxbase +14: density · scale
--size-markerThe mark of the current one at the edge2pxbase +4: scale
--surface-sunkenlight-dark(var(--n-2), var(--n-14))base +1: theme
--surface-selectedcolor-mix(in oklab, var(--a-4) 14%, transparent)one across every cell
--accent-marklight-dark(var(--a-4), var(--a-3))one across every cell

Why the kit works this way — the constitution · Open source under MIT, github.com/keshon/instrument