Flux 2.0

Component

App shell

The frame of a product: a rail of destinations, a top bar and the main column.

Plain-text source, for agents and clipboard use: /components/source/app-shell.txt

When to use it

Holds every screen of an application in one frame, so moving between areas changes the main column and nothing else. The rail lists destinations in labelled sections and marks the current one; it collapses to its icons on a wide screen and opens as a sheet from the top bar on a narrow one. The top bar carries the search entry point, reached with ⌘K, and the session context. One shell replaces the per-product sidebars that each grew their own width, colour and active state.

Not this
Not for a single task with a start and an end, such as a booking or a setup wizard; give that the Flow header so nothing pulls attention away from finishing it. Not for a marketing page or a deck. Not for switching views of one object; that is Tabs. Not for steps in order; a rail is a set of places, not a sequence.

Examples

Shell with sectioned rail

Property mapping is the current destination. Collapse the rail from its foot; at 48rem and below the rail moves behind the menu button in the top bar. The contained modifier frames the shell for this preview; an application leaves it off and fills the viewport.

Skip to content
demo / staging OG

Property mapping

Match each supplier property to its Kaptio service before content is imported.

html
<div class="flux-shell flux-shell--contained" data-flux-shell>
  <a class="flux-shell__skip" href="#shell-main">Skip to content</a>
  <nav class="flux-shell__rail" id="shell-nav" aria-label="Connect Setup">
    <a class="flux-shell__product" href="#">
      <span class="flux-shell__mark" aria-hidden="true"></span>
      <span class="flux-shell__product-name">Connect Setup<span class="flux-shell__product-meta">Kaptio Edge</span></span>
    </a>
    <div class="flux-shell__nav">
      <div class="flux-shell__section">
        <span class="flux-shell__section-label" id="shell-connectors">Connectors</span>
        <ul class="flux-shell__list" aria-labelledby="shell-connectors">
          <li><a class="flux-shell__item" href="#"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 3.5v9M3.5 8h9" /></svg><span class="flux-shell__label">New connector</span></a></li>
          <li><a class="flux-shell__item" href="#"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4.5h10M3 8h10M3 11.5h6" /></svg><span class="flux-shell__label">Requests</span><span class="flux-shell__count">3</span></a></li>
        </ul>
      </div>
      <div class="flux-shell__section">
        <span class="flux-shell__section-label" id="shell-setup">Setup</span>
        <ul class="flux-shell__list" aria-labelledby="shell-setup">
          <li><a class="flux-shell__item" href="#" aria-current="page"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 13.5V5l5-2.5L13 5v8.5M6 13.5V9h4v4.5" /></svg><span class="flux-shell__label">Property mapping</span></a></li>
          <li><a class="flux-shell__item" href="#"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2.5" y="3.5" width="11" height="9" rx="1" /><path d="M2.5 10.5l3-3 2.5 2.5 2-2 3.5 3.5" /></svg><span class="flux-shell__label">Content import</span></a></li>
          <li><a class="flux-shell__item" href="#"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="3.5" r="1.5" /><circle cx="3.5" cy="12.5" r="1.5" /><circle cx="12.5" cy="12.5" r="1.5" /><path d="M7.2 4.8l-3 6.2M8.8 4.8l3 6.2M5 12.5h6" /></svg><span class="flux-shell__label">Network import</span></a></li>
        </ul>
      </div>
      <div class="flux-shell__section">
        <span class="flux-shell__section-label" id="shell-workspace">Workspace</span>
        <ul class="flux-shell__list" aria-labelledby="shell-workspace">
          <li><a class="flux-shell__item" href="#"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="2.5" width="9" height="11" rx="1" /><path d="M6 6h4M6 8.5h4M6 11h2" /></svg><span class="flux-shell__label">Plans</span><span class="flux-shell__count">12</span></a></li>
          <li><a class="flux-shell__item" href="#"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="5.5" cy="10.5" r="2.5" /><path d="M7.3 8.7L13 3M11 5l1.5 1.5" /></svg><span class="flux-shell__label">Credentials</span></a></li>
        </ul>
      </div>
    </div>
    <div class="flux-shell__footer">
      <button class="flux-shell__item flux-shell__collapse" type="button" aria-label="Collapse navigation" data-flux-shell-collapse><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 3.5L5.5 8l4.5 4.5" /></svg><span class="flux-shell__label">Collapse</span></button>
    </div>
  </nav>
  <header class="flux-shell__topbar">
    <button class="flux-button flux-button--ghost flux-shell__menu" type="button" aria-label="Open navigation" aria-controls="shell-nav" aria-expanded="false" data-flux-shell-menu><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11" /></svg></button>
    <button class="flux-shell__search" type="button" data-flux-shell-search><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="7" cy="7" r="4" /><path d="M10 10l3.5 3.5" /></svg><span class="flux-shell__search-label">Search requests, properties and plans</span><kbd class="flux-shell__kbd" aria-hidden="true">⌘K</kbd></button>
    <div class="flux-shell__actions">
      <span class="flux-shell__context">demo / staging</span>
      <span class="flux-avatar flux-avatar--sm" role="img" aria-label="Orvar Geirsson">OG</span>
    </div>
  </header>
  <main class="flux-shell__main" id="shell-main" tabindex="-1">
    <h1>Property mapping</h1>
    <p>Match each supplier property to its Kaptio service before content is imported.</p>
  </main>
</div>

Anatomy

PartDescription
Skip linkThe first focusable element. Hidden until focused, then jumps to the main column.
RailA nav labelled with the product name, on layer-01 with a hairline against the main column.
ProductThe product mark, filled with its --flux-product-* accent, and the product name. Links to the product home.
SectionsGroups of destinations, each with a short mono label. Three or four sections at most.
ItemAn icon, a label and an optional count. The current destination carries aria-current="page".
CollapseAt the foot of the rail. Reduces the rail to its icons on a wide screen.
Top barThe menu button on a narrow screen, the search entry point, and the session context with the avatar.
MainThe page itself: its h1, then its content. It scrolls beneath the sticky top bar.

API

NameKindDescription
flux-shellclassThe grid. Fills the viewport and holds the rail, the top bar and the main column.
data-flux-shellattributeWires collapse, the narrow sheet and the search shortcut. Requires flux-components.js.
flux-shell--containedclassFrames the shell at --flux-shell-contained-height for previews and documentation. The search shortcut is not bound, so the host page keeps its own.
data-collapsedattributeOn flux-shell. Set it at render time to start collapsed, for example from a saved preference.
flux-shell__skipclassThe skip link. Its href names the main column.
flux-shell__railclassThe nav element. Give it an id for the menu button’s aria-controls and an aria-label with the product name.
flux-shell__productclassThe product link, with flux-shell__mark and flux-shell__product-name inside.
flux-shell__markclassThe square mark. Set --flux-shell-mark to the product’s accent, such as var(--flux-product-edge).
flux-shell__product-metaclassOptional span inside the product name, such as Kaptio Edge.
flux-shell__navclassThe scrolling list of sections.
flux-shell__sectionclassOne group: a flux-shell__section-label span and a flux-shell__list ul labelled by it.
flux-shell__itemclassA link with an svg icon, a flux-shell__label span and an optional flux-shell__count span.
aria-current="page"attributeOn the item for the current destination. Fills the item and sets its label in bold.
flux-shell__footerclassHolds the collapse button, a flux-shell__item with flux-shell__collapse and data-flux-shell-collapse.
flux-shell__topbarclassThe header element.
data-flux-shell-menuattributeOn a ghost Button with flux-shell__menu, aria-controls and aria-expanded. Shown at 48rem and below.
data-flux-shell-searchattributeOn the flux-shell__search button. Click and ⌘K or Ctrl K dispatch a bubbling flux-shell-search event; open the product’s search in a Modal from it.
flux-shell__actionsclassThe end of the top bar: flux-shell__context in mono and an Avatar.
flux-shell__mainclassThe main element, with the id the skip link names and tabindex="-1".

States

StateTreatment
Currentaria-current="page" fills the item in surface-selected, sets the label in bold text-brand and the icon in icon-brand. Weight and fill change, so colour is not the only signal.
HoverBackground layer-hover-01 and text-primary. Colour only; nothing moves.
CollapsedThe rail narrows to --flux-shell-rail-collapsed-width. Labels, counts and section labels stay in the accessibility tree; each item gains a title with its label, and a hairline separates the sections.
NarrowAt 48rem and below the rail leaves the grid. The menu button opens it as a sheet over a backdrop, with --flux-shell-sheet-shadow because it floats. The top bar and main column become inert while it is open.

Keyboard

KeysAction
TabSkip link first, then the rail, the top bar and the main column, in that order.
EnterOn the skip link, moves focus to the main column.
⌘K / Ctrl KActivates the search entry point from anywhere on the page.
EscapeCloses the narrow sheet and returns focus to the menu button.

Accessibility

  • Use nav, header and main, so each region is a landmark. Label the nav with the product name.
  • Set aria-current="page" on exactly one item, and update it on every client-side navigation.
  • Label each list by its section label with aria-labelledby, so "Setup, list, 3 items" is announced.
  • Keep labels in the accessibility tree when collapsed. The rail hides them visually, never with display: none.
  • Opening the sheet moves focus to the current item; closing it returns focus to the menu button.

Tokens

The tier-3 and tier-2 tokens this component binds to. Change the token, not the component.

  • --flux-shell-rail-width | -rail-collapsed-width | -topbar-height | -search-width | -contained-height
  • --flux-shell-rail-bg | -topbar-bg | -main-bg | -divider | -backdrop | -sheet-shadow
  • --flux-shell-padding | -content-padding | -section-gap | -section-label
  • --flux-shell-item-height | -item-gap | -item-padding-x | -item-radius | -icon-size
  • --flux-shell-item-text | -item-hover | -item-hover-text | -item-current-bg | -item-current-text | -item-icon | -item-current-icon
  • --flux-shell-mark | -mark-size

Do and don’t

Do

  • Name destinations by what the user finds there, such as Requests or Plans, not by the team that owns them.
  • Remember the collapsed state per user, and render data-collapsed on the server or before first paint so the rail does not jump.
  • Show a count only when it is work waiting for the user, such as open requests.
  • Put the page title in the main column’s h1, where it scrolls with the page, and keep the top bar for search and context.

Don’t

  • Do not number rail items. A rail is a set of places; a sequence belongs in a Flow.
  • Do not set the rail on a dark or brand ground. It is layer-01, like every other surface around content.
  • Do not put a count in a pill or a badge. A count is not a status.
  • Do not hide items for a role by greying them out. Leave them out, or keep them and explain on the destination why it is locked.
  • Do not animate the rail’s width when it collapses. It changes in one step.