Composites
All elements · App shell on the Composites page
Last checked 2026-10-22 · Awaiting approval · Accessibility tests
Approve App shell
Approving as
One grid of five children — brand, utility, nav, app bar and content — sharing a single placement map.
It is the only arrangement whose reading order matches DOM order, so focus never jumps back up the page.
Row one is the legacy masthead (logo on white, the navy .topMenu hugging the right corner).
The nav-toggle button collapses the nav, whose rows have no icons, so the rail leaves the grid and the
app bar and content take its column; on mobile it becomes a drawer. AppBar tabs are added dynamically by
pages.
The full-screen page is shell.html, styled
by css/classic/composites/shell.css. The specimen below is copied from shell.html by
bun scripts/design-pages/build-composite-pages.mjs — edit that file, not this one;
--check fails when they drift. The same run copies its shell around every other composite's
full-screen page.
| Axis | Values |
|---|---|
.qf-shell[data-nav] |
expanded · collapsed · open (mobile drawer) |
.qf-shell__brand › .qf-shell__logo | .qf-shell__logo-placeholder |
configurable SVG logo, or the dashed “Add logo here” slot when none is set |
.qf-shell__utility › .qf-shell__links · .qf-shell__identity |
secondary destinations and the one signed-in identity; rows are .qf-nav__item, each with an icon in a .qf-tooltip-host. When the labels no longer fit on one line (a narrow window or zoom), data-compact shows the icons and moves each label into a bottom-end tooltip. At 30rem or less the links fold into a .qf-shell__more (vertical ellipsis) menu, keeping the masthead one row. The identity opens a menu: the signed-in time, Profile, the Dark theme switch (menuitemcheckbox, the menu stays open), and Sign out. The utility strip carries no switches of its own. |
.qf-nav__item[aria-current=page] · __badge · __section
|
navigation parts: the rail is
SideNavigation (C-50), placed
by .qf-shell > .qf-nav
|
.qf-nav__row › .qf-nav__twisty · .qf-nav__item ·
.qf-nav__action
|
a row for what the link cannot contain: a group disclosure (.qf-nav__subitems) or a row action such as “new document”. Plain destinations stay a bare .qf-nav__item. |
.qf-appbar__tab[aria-selected] · __search · .qf-bell
|
app bar parts |
.qf-page-header · .qf-breadcrumbs |
content header: the title, subtitle and actions, with the path above them as a Breadcrumb (C-49) |
Guidance
When to use
- Use it once per page as the frame around every QAction screen: brand, utility links, main nav, app bar and content.
- Use it when users move between top-level areas such as Inbox, Documents and Workflows from a persistent nav.
- Use the app bar tabs when a page has a few sibling result sets, such as Active, Completed and Delegated.
- Use nav row actions when a destination needs its own commands, such as New in Documents.
When to consider something else
- Use Tabs inside the content area when the choices are views of one object rather than page-level result sets. See Tabs.
- Use a Tree when users browse a deep hierarchy as content rather than as navigation. See Tree.
- Use OverlaySystem windows for objects opened from results; the shell only provides the window area they live in. See OverlaySystem.
Usability
- Keep DOM order brand, utility, nav, app bar, content, so reading and focus order match the layout.
-
Mark the current destination with
aria-current="page"on its.qf-nav__item. -
Put a row action (
.qf-nav__action,.qf-nav__add) in a.qf-nav__rowbeside the link; keep plain destinations a bare.qf-nav__item. -
Configure one logo in
.qf-shell__brand; keep theAdd logo hereplaceholder only until the tenant supplies one. - Put theme and inbox view settings in the identity menu; the utility strip carries links only.
-
Lead each entry and saved-search row with its
.qf-icon(an entry's is fixed: inbox, files, folder, file-search, checklist for Tasks, cube for a custom object; a saved search's is the one picked in the IconPicker, in its color). -
Show each utility link's icon beside its label; once compact (
data-compact) the icon stands alone, named by its tooltip.
- Do not put an icon plus text in the brand slot; it holds one logo.
- Do not hide a nav-toggle in classic with CSS; the classic page has no toggle button at all.
- Do not nest a button inside a nav link to add a row action; use
.qf-nav__row. - Do not add more than a few app bar tabs; long sets belong in the nav or a saved-search menu.
Accessibility
Keyboard
| Keys | Action |
|---|---|
| Tab | Moves through utility links, nav rows and their buttons, the nav resize handle, the app bar and then the content, in DOM order. |
| Enter | Follows a nav link, or expands and collapses a group from its twisty button. |
| Shift+F10 or Context Menu | On a nav row, opens that row's menu and focuses its first item. |
| Arrow Left / Arrow Right | On the app bar tabs, moves to and selects the previous or next tab; Home and End go to the first and last. |
| Arrow Left / Arrow Right | On the nav resize handle, narrows or widens the nav by 1rem; Home and End jump to the minimum and maximum width. |
| Arrow Down / Arrow Up | In an open utility, identity or row menu, moves between items; Escape closes the menu and returns focus to its button. |
Roles and names
-
The main nav is
<nav class="qf-nav" aria-label="Main">, the utility strip is<nav class="qf-shell__links" aria-label="Utility">, and the app bar is a<header>. -
The binding must render
.qf-contentas the page's<main>landmark; the reference pages get<main>from the site frame. -
Twisty buttons carry
aria-expandedandaria-controls, and theiraria-labelreads Expand or Collapse plus the group name; subgroups arerole="group"witharia-labelledby. -
The nav toggle carries
aria-controlsandaria-expanded; the resize handle isrole="separator"witharia-orientation="vertical",aria-valuenow,aria-valueminandaria-valuemax. -
App bar tabs are
role="tablist"androle="tab"witharia-selected; identity settings aremenuitemcheckboxandmenuitemradioitems that keep the menu open. - Nav rows with a context menu advertise it with
aria-keyshortcuts="Shift+F10". -
The shell's first child is a
.qf-skip-linkto the.qf-content(<main>,tabindex="-1"): hidden until it takes focus, then drawn at the top-left with the focus ring (2.4.1).
Focus
- Provide a skip link to
<main>as the first focusable element on the page. -
When the mobile drawer (
data-nav="open") opens, move focus into the nav and make the rest of the shellinert; Escape must close it and return focus to the toggle. - Closing a menu with Escape returns focus to the button that opened it.
-
In compact mode (
data-compact) utility labels move into tooltips; each link must keep its text as its accessible name.
Other requirements
- The classic skin replaces the whole shell with a Mobile coming soon gate at 30rem and below, which fails 1.4.10 Reflow at 320 CSS pixels; resolve before claiming conformance.
- Twisty, row action and add buttons must meet 24 by 24 CSS pixels or have equivalent spacing (2.5.8).
-
The drawer slides in with a transform transition; honor
prefers-reduced-motion(2.3.3). - At 200% zoom the app bar's tablist shrinks and scrolls inside the bar instead of being clipped (1.4.10); collapsing the nav never hides the masthead's utility labels.
Test your implementation too. This guidance covers the element as specified here. How a screen composes it (labels, order, announcements) still needs its own keyboard and screen reader check.
Accessibility tests
Passed the gate (run 358b316 on 2026-10-22): no finding in any theme. Awaiting a person’s approval of the checklist below.
| Area | Automated | A person checks |
|---|---|---|
| General | Passed | Color is never the only cue, text alternatives say the same thing as the graphic, and the element reads the same in the light and the dark theme. |
| Zoom | Passed | At 200% zoom, at 320 CSS pixels wide and with the WCAG text-spacing overrides, nothing is lost, clipped or overlapping. |
| Keyboard | Passed | Every row of the Keyboard table works, focus is always visible and never trapped, and the tab order follows the reading order. (6 rows) |
| Screen reader | Passed | With NVDA or JAWS on Windows and VoiceOver on macOS, names, roles and states are announced as Roles and names says, and changes are announced once. (7 items) |
Approving. When the checklist passes on the checked run, record it with
bun run a11y:approve -- shell --by "Your name" and commit
a11y/approvals.json with the rebuilt pages.
All test status
Package
Consumers link one stylesheet, css/classic/index.css, which includes every element. The
files below are where this element is defined.
| Source |
css/classic/composites/shell.csscss/classic/components/actions.csscss/classic/base.css
|
|---|---|
| Specimens also use | Avatar · Box · Button · Card · Chip · Divider · EmptyState · Icon · Menu · SideNavigation · Table · Text · Toolbar · Tooltip |
| Sprites | sprites/icons.svg |
| React | Not yet available: the React binding has not started. |
Latest updates
| Date | Version | Breaking | Description |
|---|---|---|---|
| 2026-10-20 | 0.1 | No |
Footer (C-51): the app's page footer (footer.qf-footer, the contentinfo landmark):
__product (__name, __version), a
nav.__links of the accessibility statement, privacy, terms and support (24px
targets), and the __legal copyright line; data-variant="compact" puts
it on one line (components/footer.css). The App shell gains an optional footer slot,
.qf-container > .qf-footer, on the chrome under the content; shells without one
are laid out as before
|
| 2026-10-20 | 0.1 | No |
SideNavigation (C-50) catalogs the shell's .qf-nav rail as a standalone component:
its rules move from composites/shell.css to components/side-navigation.css (rows, twisties,
nested __subitems, aria-current="page" and the fill its parents keep,
__section, __badge), with a specimen outside the shell. The shell
keeps only placement (.qf-shell > .qf-nav: the nav column, clipping, the drawer
layer, the list's room under the masthead) and the fillets that join the current row to the app
bar, now off outside the shell. Element pages credit .qf-nav to SideNavigation
instead of the App shell
|
| 2026-10-20 | 0.1 | No |
Breadcrumb (C-49): the shell's .qf-breadcrumbs is cataloged and its rules move from
composites/shell.css to components/breadcrumb.css; the content header
(.qf-page-header) keeps using it. nav[aria-label="Breadcrumb"] ›
ol.__list › li.__item, every level a __link but the
current page (aria-current="page"), CSS-drawn separators, 24px targets. Long paths
wrap by default; data-overflow="truncate" keeps one line and cuts the levels above
the current page first; a deep path hides its middle (__item[hidden]) behind a
__more button that reveals it in place
|
| 2026-10-20 | 0.1 | Yes |
Classic is the only skin: css/modern/ and js/skin.js are removed, so consumers link
css/classic/index.css directly (no data-skin-sheet swap) and keep
data-skin="classic" on <html> for scripts. The skin switch is gone from the
site's app bar, the shell's identity menu and the Office review strip; theme stays. The per-skin
composite pages are single pages: composites/shell.html, inbox.html, overlay-system.html and
search.html replace the *.classic.html and *.modern.html pairs
|
| 2026-10-20 | 0.1 | No |
App shell: a skip link (.qf-skip-link, the shell's first child, to the .qf-content), hidden
until focused, then drawn at the top-left with the focus ring rather than a shadow. Classic
sidebar rows carry icons (inbox, files, folder, file-search, checklist, cube; a saved search's
own icon and color), so the twisty gutter (--qf-nav-gutter) narrows to the twisty's
24px target (--qf-space-6), the item padding (--qf-nav-item-pad) to --qf-space-1,
and the label sits --qf-nav-item-gap (--qf-space-2) from its icon. The workflows
entry reads Tasks; a saved search's row menu gains Change icon…. The utility links always show
their icons beside their labels, alone when compact. At 200% zoom the app bar's tablist shrinks
and scrolls inside the bar instead of being clipped; collapsing the nav still hides only the
rail, so the masthead's link labels stay. Classic skin
|
| 2026-09-13 | 0.1 | No | Host composite pages in the app shell and sync specimens from them |