Theme
Customize the design system

Every control starts at the preset. Change one and every preview on the site follows, on this page and the next, until you reset it.

Contrast

Text against what it sits on. Below 4.5:1 fails WCAG AA for body text — the check a reviewer runs on any surface that takes a tone.

Waiting for the first preview to report its palette.

Corners

Sets all three radii at once. The individual values are under Geometry.

Brand 5

The accent seam an app actually brands itself through. Change these and you have made the decision a real `DesignTokens.graphite(...)` call makes.

Geometry 15

Square or rounded, and how heavy every line is. The radius row above the controls sets all three corners at once, because "what does square feel like" is one decision rather than three numbers.

Surfaces 17

Everything the app is painted ON — the ground behind the content (the frame paints it too), the cards over it, the well inset in a card, and the washes that show state.

Bands 10

The rail's head and the view bar beside it (one band across the top at 960 and wider), a pushed screen's bar, the compact bar and the compact nav bar — the band's fill, its rule, its hover and active faces, the resting field box on it, and the ink and accent control that sit on it. The rail's tree and its footer stay on the ground.

Text and controls 11

The ink ramp, the control outlines and the field's focus halo. Every pair the panel can break is watched by a contrast badge above.

Status 7

The three voices that mean something — danger, caution and ok — each as a foreground and a surface wash, danger with a border as well.

Data 5

The default pin head, its glyph and its halo, and the group bands. A pin's category color comes from the palette, named in the theme seed.

Map 11

The basemap's roles — land, water, parks, roads and their casings, buildings, labels — and the pending pin's gray. A raster basemap keeps its own imagery; these paint the map's plate and any kit-drawn map.

Scrollbars 6

Every scroller's bar: the thumb at rest and while hovered or dragged, the strip it rides, and the desk bar's thickness, side gap and corners. The desk half shows only while a fine pointer is attached; at touch the platform's own overlay keeps its size.

Advanced 6

Real controls that are not decisions — motion durations, the focus ring offset, the touch floor. Opens collapsed on purpose.

Take it with you

The Dart that reproduces what you are looking at. It is a real constructor call, because the configurator edits a real preset — what you copy is what an app can paste.

Components

Chrome and containers

The surfaces that open OVER a screen — sheet, dialog, menu, overlay panel, tabs. The shell itself is not here on purpose: every screen entry renders one live at a real size, and a copy would be the worse version of the thing one click away.

Spec of record: design-system/components/surfaces/Scaffold.d.ts

0
Knobs

Live

Real widgets, built from the real kit. Turn a knob and it rebuilds; the theme bar above applies the design system's own tokens — including anything you changed under Customize — and the device selector uses the same sizes the golden baselines gate on, so what you see here is what CI holds the widget to.

Press Focus on any preview to take it to the full window — at a desktop width that renders most devices at 1:1, which is when the widget's own controls become clickable. The bar always states the device and the scale, so a shrunk preview says it is shrunk. Esc leaves.

family_chrome
Knobs
Theme
Dart for these values
family_chrome()

What it is made of

The widgets this screen assembles, with their full API below. They used to live on a second entry of their own; one thing you choose gets one page.

AppScaffold widget

The design-system shell — the one frame every app screen sits in, claiming the Material `Scaffold` archetype and morphing by M3 window class. Two hosting modes, chosen by which constructor is used: * AppScaffold.new — the original single-body render-prop mode: nav *data* (navItems / selectedIndex /…

Full dartdoc

The design-system shell — the one frame every app screen sits in, claiming the Material `Scaffold` archetype and morphing by M3 window class. Two hosting modes, chosen by which constructor is used: * AppScaffold.new — the original single-body render-prop mode: nav *data* (navItems / selectedIndex / onNavSelect / navFooter) plus one body render-prop that receives the resolved AppWindowClass. Correct when the caller already owns its own destination-switching (e.g. a router-driven shell). * AppScaffold.destinations — full `AppShell` hosting parity: a TREE of AppScaffoldDestinations and AppScaffoldDestinationGroups, each leaf built lazily and kept alive; a syncStatus slot; the transient modeline band under the bar (the active destination's AppHeaderBand contribution, rendered only while a message is carried); drawerActions; and trailing actions. Both modes share the same window-class morph (see AppWindowClass). The scaffold measures its **own** width via LayoutBuilder (not the raw viewport), so it classifies correctly inside split views and specimens, not only full screens. **ONE ROW** (`Scaffold.d.ts`, 2026-10-05, #1186 § 9 — the Google Admin console's frame, without a second band). At ≥960: ```text ┌ rail head (--bar-h) ┬ view bar (--bar-h) ─────────────────────────┐ │ ≡ ■ Console │ Records searchFilter │ ⟳ │ │ │ North Plant ⌄ · 8 records │ ├─────────────────────┼────────────────────────────────────────────┤ │ ⌂ Home │ transient band · WORKSPACE (cards) │ │ ▸ Directory • │ │ │ ─────────────────── │ │ │ ⚙ Settings │ │ │ ✉ Send feedback │ │ │ [org logo] │ │ └─────────────────────┴────────────────────────────────────────────┘ ``` * The **rail** runs the window's full height (56 ⇄ 256). Its pinned **head**, `--bar-h` tall, reads `[≡][■ mark]title` in BOTH postures (§ 10, THE MARK NEVER MOVES): the MENU BUTTON (centered in the 56 column, on the glyph line), which TOGGLES the posture — "Collapse navigation" / "Expand navigation", with its expanded state — then the railMark at start 65 (center 79) in both postures, then title (honoring brandTitle), which fades out collapsed. The collapsed head's mark OVERHANGS the 56 rail by 52, and every bar beside it — the view bar, or a pushed screen's own — leads by that much. Its pinned **footer group** (§ 12) holds the footerDestinations, the footerActions, the footerAccount and the footerBrand. The rail mounts EXTENDED at AppWindowClass.large and collapsed from 960 to 1199; the user's toggle sticks for the scaffold's life. * The **view bar** heads the content column, level with the head so the two read as one band: the view's title and, beneath it, the scope then the meta; the contributed cluster, the destination's and the shell's actions, then the divider and syncStatus LAST. It has no leading slot. Below 960 there is no rail: the single bar's managed menu button opens the overlay drawer — its header band (the brand), the SAME tree in the extended geometry and the footer group — and sync is the bar's last action. The scope leads the bar's subtitle there too. **Paint** (`Scaffold.d.ts`, #1100 re-scoped by #1186 § 9): under `chrome: accent | deep` the rail HEAD, the view bar (or a pushed bar in its place), the compact bar and the compact bottom bar paint the chrome and are chrome hosts — the head and the bar one colored band across the top. The rail's tree and footer, the workspace and the drawer stay on the ground or the card. Under `chrome: ground` every band is the ground. **THE FRAME RECEDES** (`Scaffold.d.ts`, 2026-09-29, #892): the WORKSPACE is the destination body inset by DesignTokens.workspaceGutter (14) on its inline-end and block-end edges (its inline-start meets the rail, its block-start the view bar). Below 960 the workspace takes the gutter on its inline-start too; below 600 the phone's DesignTokens.workspaceGutterC (8). The scaffold owns the inset and nothing else: a floorplan lays its content in cards inside it. **Nav data, not a nav widget.** The scaffold takes the nav *data* and constructs the AppNavRail itself with the posture the window class dictates. Selecting a destination from the drawer closes it, then fires the selection callback. **The bar's leading slot is managed below 960.** The scaffold injects the drawer's menu button into the bar's leading position only when the bar has no leading of its own — it never overwrites a caller-supplied leading. In body mode that applies to an AppHeaderBar appBar; any other PreferredSizeWidget is hosted verbatim. In destinations mode the scaffold builds its own bar internally. **THE SHORT WINDOW** (`Scaffold.d.ts`, 2026-10-13, #1270): below 960 on a window under kAppWindowShortMax tall (a phone in landscape), the destinations-mode view bar collapses UPWARD on AppMotion.move the moment the active destination's body scrolls vertically off its top, and returns when that body is back at its top — per destination. A body overflowing by no more than the bar keeps it, search mode keeps it, a horizontal scroll never toggles it, the top safe-area inset stays painted in the bar's fill, and a collapsed bar is out of the focus order and the semantics tree. The transient band, the Fab and the bottom nav stay. Body mode's appBar is the host's and is unchanged (only `DetailScreen`'s page opts in). **The sync/persistence split button** is the consumer's to wire into syncStatus (or, in body mode, the bar's own `syncStatus` SLOT, never its `actions` list) — the scaffold hosts it, it does not synthesize the control. Direction-agnostic: the rail sits at the layout start edge, the drawer slides from the start edge, and the FAB floats at the end edge — all via logical positioning, so RTL mirrors them with zero edits. ```dart // Body mode. AppScaffold( title: 'Records', appBar: const AppHeaderBar(title: 'Records', meta: '128'), navItems: const [ AppNavItem(glyph: KitGlyph.table, label: 'Records'), AppNavItem(glyph: KitGlyph.map, label: 'Map'), ], selectedIndex: index, onNavSelect: (i) => setState(() => index = i), navFooter: const [AppNavItem(glyph: KitGlyph.gear, label: 'Settings')], syncStatus: SyncStatusButton(...), fab: AppFab(glyph: KitGlyph.plus, tooltip: 'New record', onPressed: create), body: (context, windowClass) => RecordsView(windowClass: windowClass), ) // Destinations mode — a tree. AppScaffold.destinations( title: 'Console', destinations: [ AppScaffoldDestination( glyph: KitGlyph.house, label: 'Home', builder: (context) => const HomeView(), ), AppScaffoldDestinationGroup( key: 'directory', glyph: KitGlyph.users, label: 'Directory', items: [ AppScaffoldDestination( label: 'Users', badge: const AppNavBadge.count(3), builder: (context) => const UsersView(), ), AppScaffoldDestination( label: 'Groups', builder: (context) => const GroupsView(), ), ], ), ], // Settings has one home: the footer group, below the tree. footerDestinations: [ AppScaffoldDestination( glyph: KitGlyph.gear, label: 'Settings', builder: (context) => const SettingsView(), ), ], footerActions: [ AppScaffoldFooterAction( glyph: KitGlyph.message, label: 'Send feedback', onSelect: sendFeedback, ), ], syncStatus: SyncStatusButton(...), railMark: const AppRailMark(glyph: KitGlyph.check), scope: AppScaffoldScope( kind: 'Project', label: activeProject.name, onSelect: (anchor) => pickProject(anchor), ), ) ```

AppScaffolddefault constructor

Knob Type Default What it does
appBar PreferredSizeWidget? null The optional bar — typically an `AppHeaderBar`. Body mode only; in destinations mode the scaffold builds its own bar and this is null. At ≥960 with a nav surface it is the VIEW BAR, in the content column beside the rail's head; below 960 it spans the window, and when it is an `AppHeaderBar` with no…
apps AppScaffoldApps? null THE APP SWITCHER (`Scaffold.d.ts`, steady-core #1841): with two or more apps the BRAND becomes one menu button over them — the rail head at >=960 (the mark, the title and a chevron; the mark alone collapsed) and the drawer's header band below it. NEVER the compact bar, whose title keeps the scope…
body * Widget Function(BuildContext context, AppWindowClass windowClass) — Body-mode render-prop. Builds the body for the resolved window class; receives the live AppWindowClass so the body can set sheet posture, toolbar collapse, column counts, etc. off the same measured value the shell uses. Unused in destinations mode.
brandTitle AppBrandTitle
shown · hidden
AppBrandTitle.shown Whether the brand sets title beside railMark — in the rail head at ≥960 and the drawer's header band below. AppBrandTitle.hidden draws the mark alone and keeps title as its accessible name; with no railMark the title shows regardless. The view bar's title is unaffected.
fab Widget? null Optional floating action button, pinned to the thumb zone at the inline-end edge (16px inset on compact, 24px on medium/expanded). Null renders no FAB. The caller supplies any `FloatingActionButton` (or an equivalent action cluster); the scaffold owns only its placement.
footerAccount AppNavFooterAccount? null THE ACCOUNT ITEM (`Scaffold.d.ts`, steady-core #1739): the signed-in person, one row at the foot of the footer group — under the footer destinations and actions, above the footerBrand — forwarded to AppNavRail.footerAccount in the rail (both postures) and the drawer. In the drawer each item closes…
footerActions List<AppScaffoldFooterAction> const [] THE FOOTER ACTIONS (`Scaffold.d.ts` § 12, #1186): global commands with no body ("Send feedback", "Help"), pinned in the rail's and the drawer's footer group after the footer destinations — never lit, outside the destination index, icon-only with their label as the accessible name and tooltip in the…
footerBrand AppScaffoldFooterBrand? null THE FOOTER BRAND (`Scaffold.d.ts` § 12, #1186): an organization's or partner's logo, pinned at the very bottom of the rail and the drawer — its `mark` extended and in the drawer, its `compact` (or nothing) in the collapsed rail. Not focusable, not interactive. Null renders none.
navFooter List<AppNavItem>? null Body-mode optional second nav group pinned to the bottom of the rail/drawer behind a hairline separator (settings/account) — forwarded to AppNavRail.footer. Unused (null) in destinations mode.
navItems * List<AppNavNode> — Body-mode primary navigation — the same TREE AppNavRail.items takes (leaves and groups). The scaffold builds the AppNavRail from it, re-posturing per window class. Empty in destinations mode.
onNavSelect * ValueChanged<int> — Body-mode selection callback — fires with the tapped leaf's depth-first index among navItems. When the tap comes from the overlay drawer, the drawer closes first. Unused in destinations mode.
railMark Widget? null Brand/app mark — typically an AppRailMark, carrying the app's `AppBrandMark` in its `mark`. IDENTITY ONLY (`Scaffold.d.ts` § 11): it says whose app this is and is never a control. * **≥960** (a nav surface standing): the rail HEAD, beside the menu button, at the same place in both postures (§ 10). *…
scope AppScaffoldScope? null THE ACTIVE SCOPE (`Scaffold.d.ts` § 11, THE SCOPE LEADS THE BAR'S SUBTITLE, #1186) — which slice of the data the user is working in. It is the FIRST item on the view bar's subtitle line, before the meta (`North Plant ⌄ · 8 records`), at every window class and posture: a quiet text button with a…
selectedIndex * int? — Body mode: the active LEAF's depth-first index among navItems, or null if none is selected. Destinations mode: null (the default) keeps selection **uncontrolled** — the scaffold owns it, seeded by initialIndex. Non-null makes selection **controlled**: the host is the source of truth (a router-driven…
syncStatus Widget? null The sync-status control — the app's one global control, the bar's LAST inline-end action at every window class (the divider before it at ≥960 when the bar carries a view cluster). Null renders nothing. In body mode it fills the appBar's own `syncStatus` slot when that is empty.
title String? null The shell title — the APP's name, said in the rail head's brand at ≥960 and the drawer's header band below it (brandTitle can hide it beside a mark). In destinations mode it is **required**, and it is also the view bar's title fallback (per-destination AppScaffoldDestination.title wins when set).

AppScaffold.destinations

Knob Type Default What it does
actions List<Widget> const [] Destinations-mode trailing bar actions, rendered after the active destination's own AppScaffoldDestination.actions — secondary controls that apply regardless of which destination is active. Empty (the default) adds nothing.
apps AppScaffoldApps? null THE APP SWITCHER (`Scaffold.d.ts`, steady-core #1841): with two or more apps the BRAND becomes one menu button over them — the rail head at >=960 (the mark, the title and a chevron; the mark alone collapsed) and the drawer's header band below it. NEVER the compact bar, whose title keeps the scope…
brandTitle AppBrandTitle
shown · hidden
AppBrandTitle.shown Whether the brand sets title beside railMark — in the rail head at ≥960 and the drawer's header band below. AppBrandTitle.hidden draws the mark alone and keeps title as its accessible name; with no railMark the title shows regardless. The view bar's title is unaffected.
compactNav AppCompactNavigation
drawer · bar · dock
AppCompactNavigation.drawer Destinations-mode compact-width navigation posture. The default, AppCompactNavigation.drawer, keeps the overlay drawer behind a menu button. AppCompactNavigation.bar and AppCompactNavigation.dock host the tree's DEPTH-0 LEAVES, in order, up to kAppScaffoldPhoneNavCapacity — a group never rides them,…
destinations * List<AppScaffoldNavNode>? — Destinations-mode navigation TREE — leaves (AppScaffoldDestination) and groups (AppScaffoldDestinationGroup), at most three levels deep. Null in body mode. Every leaf's AppScaffoldDestination.builder runs once into an IndexedStack and is kept alive so scroll offset and in-progress state survive…
drawerActions List<AppScaffoldDrawerAction> const [] Destinations-mode global drawer actions (sign-out, about, etc.) — bodiless commands in the drawer rail's footer group, after any footerDestinations. Empty (the default) adds nothing.
fab Widget? null Optional floating action button, pinned to the thumb zone at the inline-end edge (16px inset on compact, 24px on medium/expanded). Null renders no FAB. The caller supplies any `FloatingActionButton` (or an equivalent action cluster); the scaffold owns only its placement.
footerAccount AppNavFooterAccount? null THE ACCOUNT ITEM (`Scaffold.d.ts`, steady-core #1739): the signed-in person, one row at the foot of the footer group — under the footer destinations and actions, above the footerBrand — forwarded to AppNavRail.footerAccount in the rail (both postures) and the drawer. In the drawer each item closes…
footerActions List<AppScaffoldFooterAction> const [] THE FOOTER ACTIONS (`Scaffold.d.ts` § 12, #1186): global commands with no body ("Send feedback", "Help"), pinned in the rail's and the drawer's footer group after the footer destinations — never lit, outside the destination index, icon-only with their label as the accessible name and tooltip in the…
footerBrand AppScaffoldFooterBrand? null THE FOOTER BRAND (`Scaffold.d.ts` § 12, #1186): an organization's or partner's logo, pinned at the very bottom of the rail and the drawer — its `mark` extended and in the drawer, its `compact` (or nothing) in the collapsed rail. Not focusable, not interactive. Null renders none.
footerDestinations List<AppScaffoldDestination> const [] Destinations-mode footer destinations — whole LEAF destinations pinned BELOW the nav surface's scrolling tree behind its hairline: AppNavRail.footer on the inline rail, the drawer rail's footer group (ahead of drawerActions) below expanded width. `Scaffold.d.ts` THE FOOTER GROUP. This is where an…
initialIndex int 0 Destinations-mode index selected on first build (uncontrolled selection only — ignored when selectedIndex is non-null).
leading Widget? null Destinations-mode custom bar leading widget. Null lets the scaffold manage the slot itself (the drawer's menu button below 960; nothing at ≥960, where the rail head's menu button is the nav affordance).
onDestinationChanged ValueChanged<int>? null Destinations-mode callback — fires whenever the selected destination changes (uncontrolled), or whenever a destination is tapped (controlled — the host then moves selectedIndex).
pushed bool false A route PUSHED inside the lit destination (a record's detail or edit) is showing, and it draws its own bar (`Scaffold.d.ts` `pushed`, #1186). * **≥960**: the rail stays, its head and footer included (the lit destination stays lit); the view bar and the transient band are NOT drawn — the pushed…
railMark Widget? null Brand/app mark — typically an AppRailMark, carrying the app's `AppBrandMark` in its `mark`. IDENTITY ONLY (`Scaffold.d.ts` § 11): it says whose app this is and is never a control. * **≥960** (a nav surface standing): the rail HEAD, beside the menu button, at the same place in both postures (§ 10). *…
scope AppScaffoldScope? null THE ACTIVE SCOPE (`Scaffold.d.ts` § 11, THE SCOPE LEADS THE BAR'S SUBTITLE, #1186) — which slice of the data the user is working in. It is the FIRST item on the view bar's subtitle line, before the meta (`North Plant ⌄ · 8 records`), at every window class and posture: a quiet text button with a…
selectedIndex int? null Body mode: the active LEAF's depth-first index among navItems, or null if none is selected. Destinations mode: null (the default) keeps selection **uncontrolled** — the scaffold owns it, seeded by initialIndex. Non-null makes selection **controlled**: the host is the source of truth (a router-driven…
syncStatus Widget? null The sync-status control — the app's one global control, the bar's LAST inline-end action at every window class (the divider before it at ≥960 when the bar carries a view cluster). Null renders nothing. In body mode it fills the appBar's own `syncStatus` slot when that is empty.
title * String? — The shell title — the APP's name, said in the rail head's brand at ≥960 and the drawer's header band below it (brandTitle can hide it beside a mark). In destinations mode it is **required**, and it is also the view bar's title fallback (per-destination AppScaffoldDestination.title wins when set).

AppHeaderActions widget

Projects header-bar actions from a destination's BODY into the hosting AppScaffold.destinations view bar — the seam behind the Table View v3 bar posture, where a records surface's search + view controls live in the header at medium/expanded width while their STATE stays inside the surface. Mount it…

Full dartdoc

Projects header-bar actions from a destination's BODY into the hosting AppScaffold.destinations view bar — the seam behind the Table View v3 bar posture, where a records surface's search + view controls live in the header at medium/expanded width while their STATE stays inside the surface. Mount it anywhere in a destination's subtree (it renders nothing in place); the scaffold renders children at the bar's inline end, ahead of AppScaffoldDestination.actions, AppScaffold.actions and the sync control, so the constitution's ordering (sync last) is preserved — as the bar's view cluster (AppHeaderBar.center, divided from sync) in the resting-field and search postures, and as the leading bar actions in the resting-glyph posture. The seam is view-agnostic: any records view — table, map, deck, gallery, dashboard — contributes the same chrome cluster against its own row stream. Each destination has its own registration; switching destinations swaps the contributed set with the body. Re-building with new children updates the header; unmounting clears it. It also carries the bar's POSTURE: a contribution declaring searchMode takes the whole bar for the duration (`back · children · syncStatus`), which is how a records surface expands its search on demand instead of resting a toolbar in the chrome (`AppBar.d.ts` resting rule). ONE contributor per destination surface — a second mount overwrites the first (last-in wins), which is never what you want. Outside a destinations-mode AppScaffold (body mode, tests, a bare route) there is no scope and the widget is inert — check AppHeaderActions.availableIn to fall back to an in-body toolbar, as `RecordsTableScaffold` does. The band under the bar is the sibling seam: AppHeaderBand.

AppHeaderActionsdefault constructor

Knob Type Default What it does
children * List<Widget> — The contributed action widgets.
fabDeck ValueListenable<KitFabDeckEdge?>? null The live edge of a record DECKED under this surface's collection below 960 (`RecordsScreen.d.ts` THE FAB RIDES THE DECK, 2026-10-01, #963): while it holds an edge the scaffold's Fab lifts to stand its usual inset above the deck's top edge, tracking it frame by frame, and stands down while the edge…
fabEndInset double 0 How far the scaffold's Fab stands in from its inline-end corner while this surface has a record pane DOCKED beside its collection (`RecordsScreen.d.ts` THE FAB RIDES THE RECORDS REGION, #932): the pane's slot, so the Fab anchors to the collection's end edge. Zero (the default) keeps the corner. A…
onExitSearch VoidCallback? null Collapses searchMode — wired to the bar's back affordance, which is the only control the posture leaves standing. Required when searchMode is true, ignored otherwise.
paneOpen bool false Whether this surface currently has an IN-FLOW pane open on its inline-end edge (the view-options pane, a docked detail pane). The scaffold suppresses its Fab while one is. An in-flow pane is a sibling in the BODY, and the Fab floats over the whole body — so it lands on the pane's own footer…
presence Widget? null THE CONTRIBUTED PRESENCE (`Scaffold.d.ts` `ScaffoldHeaderActionsProps.presence`, steady-core #1331): the body's `AppPresenceStack` (`Presence.d.ts` EVERY FLOORPLAN CARRIES IT AS A KNOB). The scaffold draws it in the bar's presence slot — after the contributed controls and every action, immediately…
restingField bool false Whether the contribution is the >=960 RESTING-FIELD posture (`Scaffold.d.ts` `restingField`, renamed from `centered` with the Contained MAJOR — the field no longer centers). False (the default) is the resting-GLYPH posture: one affordance beside sync, at every width it rests as an icon. True rests…
searchMode bool false Swaps the bar into the SEARCH posture (`Scaffold.d.ts` SEARCH MODE): `back · children · syncStatus`, with the title, the destination's own actions, the shell's actions and the primary slab all yielding until the mode exits — search is a mode, not a layout. Sync has no mode exception, and the band is…

AppHeaderBand widget

Projects a destination's TRANSIENT message into the scaffold's modeline BAND — the 28px strip under the app bar (`Scaffold.d.ts` THE BAND, transient-only since 2026-08-24): the band exists only while a message (or its paired action) is in flight, and a destination messaging through it renders no…

Full dartdoc

Projects a destination's TRANSIENT message into the scaffold's modeline BAND — the 28px strip under the app bar (`Scaffold.d.ts` THE BAND, transient-only since 2026-08-24): the band exists only while a message (or its paired action) is in flight, and a destination messaging through it renders no footer modeline. The standing segments and the scope segment are DELETED — the counts restated what the surface shows, and the scope leads the bar's subtitle (AppScaffold.scope). Mount it anywhere in a destination's subtree; it renders nothing in place, exactly like AppHeaderActions, and follows the same rules: ONE contributor per destination surface (a second mount overwrites the first), each destination keeps its own registration, and re-building with new values repaints the band while unmounting clears it. Outside a destinations-mode AppScaffold — body mode, a bare route, a test — the widget is inert; check availableIn and keep a footer AppModeline there instead, mounted for as long as the surface can message (`Modeline.d.ts` SILENT REGION): silent, it draws nothing.

AppHeaderBanddefault constructor

Knob Type Default What it does
actionLabel String? null Label for the single action beside message (the Undo pattern).
message String? null The transient message, e.g. `'3 records deleted'`. The HOST owns its timing — clear it when its moment has passed.
onAction VoidCallback? null Callback for that single action.

AppHeaderHostBoundary widget

Hides the scaffold's header seams from a subtree — after this widget, AppHeaderActions.availableIn and AppHeaderBand.availableIn both read FALSE, and a contributor mounted inside is inert. It exists for the one case where a surface is inside a destination's body and must NOT reach that destination's…

Full dartdoc

Hides the scaffold's header seams from a subtree — after this widget, AppHeaderActions.availableIn and AppHeaderBand.availableIn both read FALSE, and a contributor mounted inside is inert. It exists for the one case where a surface is inside a destination's body and must NOT reach that destination's bar: a whole-screen composition nested inside another one (a records view embedded in a detail screen's related slot). Without it the nested surface contributes its search field to the HOST screen's bar — the scope is perfectly visible from inside a docked pane, because a pane is a sibling in the body — and the two surfaces then fight over one bar, last-in wins. The subtree keeps its in-body fallbacks, which is exactly what the seam's own contract says happens where there is no host: an in-body toolbar and a footer modeline (`Scaffold.d.ts`, "a bare route, a test").

AppHeaderHostBoundarydefault constructor

Knob Type Default What it does
child * Widget — The subtree that must not see a bar host.

KitSheet widget

The design-system Sheet — a themed, token-driven modal surface claiming the M3 bottom-sheet archetype (bottom posture) or a hand-built end-anchored panel (side posture, no M3 equivalent shipped in Flutter yet). Renders the shared anatomy for both postures: the required title + an optional quiet meta…

Full dartdoc

The design-system Sheet — a themed, token-driven modal surface claiming the M3 bottom-sheet archetype (bottom posture) or a hand-built end-anchored panel (side posture, no M3 equivalent shipped in Flutter yet). Renders the shared anatomy for both postures: the required title + an optional quiet meta annotation in the header (e.g. a live result count "14 match" — filters apply LIVE; this is quiet header metadata, never a staged "Show N records" commit control), the body content, and an optional footer of quiet actions (e.g. a ghost "Clear all" — **no slab inside a sheet**; the accent stays with the view's primary action elsewhere). The `bottom` posture additionally renders a grab bar above the header; the `side` posture renders a hairline start-edge instead. Use showKitSheet rather than constructing this widget directly in app code — it wires posture selection, the scrim, and focus/dismiss. This widget is exposed publicly so goldens and composed call sites can render the chrome directly (e.g. to specimen both postures without an overlay). ```dart KitSheet( posture: KitSheetPosture.bottom, title: 'Filter', meta: const Text('14 match'), footer: AppButton(label: 'Clear all', onPressed: clearAll), body: filterRows, ) ```

KitSheetdefault constructor

Knob Type Default What it does
body * Widget — The sheet's main content (e.g. filter controls). Required — a sheet with no body is not a meaningful surface.
expandBody bool false Forces the body to FILL the sheet's height so the footer pins to the bottom edge — what a fixed-height bottom posture needs (showKitSheet's `keyboardStable` passes it). The side posture fills regardless (full height by contract); the default bottom posture stays content-sized.
footer Widget? null Optional trailing row of quiet actions (e.g. a ghost "Clear all"). Per the constitution, a sheet never hosts an accent slab — footer content should stay ghost/outline.
headerAction Widget? null ONE quiet header affordance, rendered after meta and before the ✕ (Sheet.d.ts `headerAction`) — the view-options pane's Export menu trigger. Quiet grammar only; never a slab, never a commit control.
meta Widget? null Optional annotation next to the title (e.g. a live result count "14 match") — quiet header metadata, never a commit-button substitute. A node, as `Sheet.d.ts` types it (`meta?: React.ReactNode`): the header sets it in the quiet data face (DesignTokens.data, DesignTokens.ink60) through a…
onClose VoidCallback? null Optional explicit close affordance (✕) in the header. When null, no ✕ renders — callers relying on showKitSheet still get barrier-tap and Esc dismissal regardless of whether a ✕ is present.
posture * KitSheetPosture
bottom · side
— Which M3 posture this sheet renders as. See KitSheetPosture.
title * String — The sheet's heading, rendered in DesignTokens.title — and its accessible NAME in every posture. **Required**, and a plain `String`, since the 2026-09-10 pin (Sheet.d.ts `title: string`): a modal sheet takes focus, so an unnamed one is a trap announced as "dialog" and nothing else, and a standard…
titleContent Widget? null Optional widget rendered in the title position instead of the title text (Sheet.d.ts `titleContent` — the two-prop split is this kit's own, mirrored upstream by the same pin) — the view-options pane's tab row is the consumer. The header band is the pane's ONE orientation band: a generic title…

AppDialog widget

The design-system Dialog — a themed, token-driven destructive-confirm surface with the M3 alert dialog's anatomy, lifted by DesignTokens.sh3. **Destructive confirms only.** Anatomy is fixed: a factual title stating the consequence with a count where relevant (e.g. "Delete 3 records?"), a message…

Full dartdoc

The design-system Dialog — a themed, token-driven destructive-confirm surface with the M3 alert dialog's anatomy, lifted by DesignTokens.sh3. **Destructive confirms only.** Anatomy is fixed: a factual title stating the consequence with a count where relevant (e.g. "Delete 3 records?"), a message naming the consequence in DesignTokens.body / ink60 (e.g. "Removes 3 records permanently."), a ghost **Cancel** action, and a **confirm** action whose confirmLabel is a verb (e.g. "Delete") rendered as AppButton with `variant: AppButtonVariant.outline, tone: AppButtonTone.danger` — the danger-outline confirm is the only sanctioned destructive affordance; a danger *slab* does not exist in this system. Use showAppDialog rather than constructing this widget directly in app code — it wires the scrim, focus management, and barrier/Esc-cancels-false contract. This widget is exposed publicly so goldens and composed call sites (e.g. a caller building its own host around the anatomy) can render it standalone. ```dart AppDialog( title: 'Delete 3 records?', message: 'Removes 3 records permanently.', confirmLabel: 'Delete', onConfirm: doDelete, onCancel: () => Navigator.of(context).pop(false), ) ```

AppDialogdefault constructor

Knob Type Default What it does
cancelLabel String 'Cancel' The cancel action's label. Defaults to "Cancel".
confirmLabel * String — The confirm action's label — a verb (e.g. "Delete", "Remove", "Discard"), never a generic "OK" or "Yes".
content Widget? null Optional body content rendered BELOW message — the one input a destructive confirm may collect before it commits. `Dialog.d.ts` has always typed its body as `children: React.ReactNode`; this port narrowed it to a `String` message, which is why the pending panel's evacuation note had nowhere to go.…
danger bool true Whether the confirm is the DANGER outline (the default) or the accent outline (`Dialog.d.ts` `danger?: boolean`, default true; the web's `danger={false}`), for a confirm whose action is consequential but not destructive. Never a slab either way.
message * String — The consequence, stated as fact (e.g. "Removes 3 records permanently."). Rendered in DesignTokens.body at DesignTokens.ink60 — never an exclamation, never "Oops".
onCancel VoidCallback? null Fires when the cancel action is tapped. Optional — a directly constructed AppDialog with no onCancel simply renders a non-functional Cancel button (callers normally reach this widget only through showAppDialog, which always supplies one).
onConfirm * VoidCallback — Fires when the confirm action is tapped. The dialog itself does not pop — showAppDialog wires that; a directly-constructed AppDialog leaves dismissal to the caller's onConfirm / onCancel.
title * String — The factual question, stated with a count where relevant (e.g. "Delete 3 records?"). Rendered in the dialog's title role — no "Are you sure?" framing (field-manual voice: state the fact, not the anxiety).

AppMenu widget

The design-system Menu — a themed, token-driven hairline popover claiming the M3 menu archetype. Anchors a caller-built trigger to a MenuAnchor popover listing items in order. trigger wires its own tap (typically `onPressed: toggle` on an `AppIconButton` or `AppButton`) rather than AppMenu wrapping…

Full dartdoc

The design-system Menu — a themed, token-driven hairline popover claiming the M3 menu archetype. Anchors a caller-built trigger to a MenuAnchor popover listing items in order. trigger wires its own tap (typically `onPressed: toggle` on an `AppIconButton` or `AppButton`) rather than AppMenu wrapping it in a second gesture detector — the trigger keeps full ownership of its own press/hover/focus state, exactly as it would unwrapped. AppMenu merges the popover's `expanded` state onto the trigger's own Semantics node (so a plain `AppIconButton(active: open, onPressed: toggle)` trigger reports `button: true, expanded:` correctly with no extra wiring); MenuAnchor gives native arrow-key navigation between items for free. An item with AppMenuItem.dividerBefore gets a hairline Divider rendered immediately above it — the spec's item-grouping grammar. The current item (AppMenuItem.checked) carries a trailing ✓ in the data font AND reports its checkable state to a screen reader; AppMenuItem.danger items paint DesignTokens.danger — never a red slab. ```dart AppMenu( trigger: (context, controller, toggle, open) => AppIconButton( glyph: KitGlyph.dots, title: 'More', active: open, expanded: open, onPressed: toggle, ), items: [ AppMenuItem(label: 'Rename view', onSelected: rename), AppMenuItem( label: 'Density: compact', checked: true, onSelected: toggleDensity, ), AppMenuItem( label: 'Delete view', danger: true, dividerBefore: true, onSelected: delete, ), ], ) ```

AppMenudefault constructor

Knob Type Default What it does
align AppMenuAlign
start · end
AppMenuAlign.start Which edge of the trigger the panel aligns to (`Menu.d.ts` `align`). Either way it slides inward rather than cross the window's edge. Default AppMenuAlign.start.
controller MenuController? null Opens and closes the popover from OUTSIDE the trigger — the seam a host opens a row's ⋯ through by key (`Menu.d.ts` `contextmenu`; a data table's ⇧F10 on its cell cursor, `DataTable.d.ts` THE EDITING GRAMMAR). Null: the menu keeps its own, and only its trigger opens it.
heading AppMenuHeading? null THE HEADING (`Menu.d.ts`, #1739): a non-interactive block above items, with a DesignTokens.hairS divider under it. It scrolls with the items under the height cap rather than pinning, is a header node to a screen reader, and is never in the roving focus — focus still lands on the first enabled item.…
items * List<AppMenuItem> — The popover's rows, in display order.
trigger * AppMenuTriggerBuilder — Builds the widget that opens/closes the popover. See AppMenuTriggerBuilder.

AppOverlayPanel widget

A floating panel over imagery — paper fill, the surface frame (DesignTokens.surfaceEdge, the structural hairline by default), surface radius, and the popover shadow slot. The overlay treatment `CardTheme` does not express, built straight from the tokens.

AppOverlayPaneldefault constructor

Knob Type Default What it does
child * Widget — The panel's content.
padding * EdgeInsetsGeometry — Inner padding around child.

AppTooltip widget

The design-system Tooltip: the name an icon-only child carries, shown on hover after kAppTooltipHoverDelay, at once on keyboard focus, and on a long press on touch (which does not press the control). message is the control's own accessible name, repeated — the child keeps its own label (an explicit…

Full dartdoc

The design-system Tooltip: the name an icon-only child carries, shown on hover after kAppTooltipHoverDelay, at once on keyboard focus, and on a long press on touch (which does not press the control). message is the control's own accessible name, repeated — the child keeps its own label (an explicit `Semantics(label:)` over an excluded subtree, as every kit control does). enabled false wires nothing (the label is on screen already, as in the extended rail). ```dart AppTooltip(message: 'Filter', child: pressable) AppTooltip(message: 'Inbox', side: AppTooltipSide.end, child: row) AppTooltip(message: 'Reorder tile', longPress: false, child: grip) AppTooltip(message: 'Due date', excludeSemantics: true, child: head) ```

AppTooltipdefault constructor

Knob Type Default What it does
child * Widget — The ONE control it names.
enabled bool true False wires nothing and draws nothing.
excludeSemantics bool false True adds nothing to the semantics tree: no message as a tooltip and no `MergeSemantics`, so child's semantics stay exactly where they were (`Tooltip.d.ts` THE WORDS ARE THE CONTROL'S NAME, REPEATED). For a child that is part of its HOST's node rather than a node of its own: a DataTable column head…
longPress bool true False: hover and keyboard focus only, no touch long press (`Tooltip.d.ts` WHEN IT SHOWS `longPress: false`), for a control whose hold is its host's own gesture — a drag grip whose hold lifts a tile, a word under a tile's hold-to-arrange.
message * String — The words: the control's accessible name, repeated. Plain text.
side AppTooltipSide
below · above · end · start
AppTooltipSide.below The preferred side; it flips when that side lacks room.

AppHeaderBar widget

The design-system AppBar — the view's top bar, claiming the M3 top-app-bar archetype. Slots: optional leading (nav toggle / back — typically an `AppIconButton`), title (the **view's** name, not the app's — rendered in the headline role, DesignTokens.headline), optional meta (a quiet line BENEATH the…

Full dartdoc

The design-system AppBar — the view's top bar, claiming the M3 top-app-bar archetype. Slots: optional leading (nav toggle / back — typically an `AppIconButton`), title (the **view's** name, not the app's — rendered in the headline role, DesignTokens.headline), optional meta (a quiet line BENEATH the title — "46 of 68 work orders · synced 2 min ago" — in the label role at DesignTokens.ink40, the UI font, never mono), optional center (the view cluster), actions (a row of widgets laid out inline-end in the given order — typically `AppIconButton`s or at most one slab `AppButton`), and syncStatus (the sync control, always rendered last). Consumer guidance (documented, not mechanically enforced — this widget takes any Widgets in actions and does not inspect them): at most **one** slab among actions, the constitution's one-accent-slab-per- surface rule. **The sync control is NOT one of actions.** It has its own syncStatus slot and the widget renders it after everything in actions, so "sync is always the last inline-end action" is structural rather than a rule each host has to remember — which is exactly how it went missing from every route-pushed screen in the suite while every scaffold-hosted one had it. Implements PreferredSizeWidget so it drops directly into `Scaffold.appBar`. **THE CONTAINED BAR** (`AppBar.d.ts`, 2026-09-29, #892): the bar IS the ground (DesignTokens.sur) — no rule beneath it, no shadow, elevation 0 — the same contract `appTheme()` wires onto Material's own `AppBarTheme`. Its height is `--bar-h` (DesignTokens.barH: 64 comfortable, 56 at the desk) and its controls take `--ctl` like every bar control — nothing inside re-scopes the density. When it carries both a view cluster (center) and syncStatus, a 1 × 20 DesignTokens.hairS divider separates them: the view's controls on one side, the app's one global control on the other. **The bar shares the workspace's edges** (`AppBar.d.ts`, 2026-09-29): it carries no inline padding of its own beyond the workspace inset, so under an `AppScaffold` destination its first control starts on the workspace's inline-start edge and its last ends on the inline-end edge — 0 on the rail side and DesignTokens.workspaceGutter at the window edge at >=960, the gutter on both sides from 600 to 959, and DesignTokens.workspaceGutterC on both below 600. The scaffold hands those edges down; a bar with no scaffold workspace under it (a body-mode host, a route-pushed floorplan) keeps the symmetric `--appbar-px` (10). Its icon controls sit on the ground, so a ghost one hovers with DesignTokens.groundHover rather than the card's DesignTokens.ink4. **Owns its top safe area**, exactly as Material's `AppBar` does: on an edge-to-edge window (Android 15 enforces it) the bar's surface extends up behind the status bar while the content row lays out below the inset, and an `AnnotatedRegion` keeps the status icons contrasting against DesignTokens.sur. preferredSize is the comfortable bar height — the slot's CAP: the bar lays itself out at DesignTokens.barH, and `Scaffold` starts its body where the bar actually ends, so the desk's 56 bar gives its 8px back to the body. A primary `Scaffold` adds `MediaQuery.padding.top` to the app-bar slot itself. Hosted under an ancestor `SafeArea` (the form view's body) the inset is already consumed and the bar renders at spec height unchanged. **Adaptation strategy: scale.** This widget renders exactly the slots it's given at every window class; it does not decide what moves where. The spec's compact-class swap (primary actions leaving the bar for a FAB cluster, the action row collapsing to essentials + overflow) is the future `AppScaffold`'s job — it composes AppHeaderBar and decides, per window class, which action-tier widgets it hands to actions versus routes elsewhere. AppHeaderBar itself never inspects window class. Direction-agnostic: leading sits at the layout start edge, actions at the end edge, via `EdgeInsetsDirectional` / `Row` (which already mirrors under RTL) — no physical left/right ever appears here. ```dart Scaffold( appBar: AppHeaderBar( leading: AppIconButton(glyph: KitGlyph.menu, title: 'Navigation', onPressed: openDrawer), title: 'Records', meta: '128', actions: [ AppButton(variant: AppButtonVariant.slab, icon: const KitIcon(KitGlyph.plus), label: 'New', onPressed: create), ], syncStatus: SyncStatusButton(state: syncState, pending: pending, onSyncNow: syncNow), ), body: ..., ) ```

AppHeaderBardefault constructor

Knob Type Default What it does
actions List<Widget> const [] Trailing action row, laid out inline-end in the given order — typically `AppIconButton`s or at most one slab `AppButton`. See the class doc for the (unenforced) constitution guidance on ordering and slab count. Empty renders no action space.
center List<Widget> const [] The VIEW CLUSTER — the records-view chrome seam (AppBar.d.ts `center`): at >=960 the resting search field and its labeled view-options control, in search mode the expanded field. Any records view (table, map, deck, gallery, dashboard) projects its narrowing chrome here. It is pinned to the INLINE…
leading Widget? null Optional leading slot — a nav toggle / back control (typically an `AppIconButton`). Renders at the layout start edge. Null renders no leading space; `Scaffold` still auto-implies its own drawer/back button ahead of this widget when applicable.
meta String? null Optional quiet line rendered BENEATH title (`AppBar.d.ts` THE CONTAINED BAR) — a record count read as a sentence, a last-synced time — in the label role at DesignTokens.ink40, the UI font. The pair is vertically centered in the bar, and meta yields width before the title does. Null renders no meta…
presence Widget? null THE PRESENCE SLOT (`AppBar.d.ts`, steady-core #1307): the view scope's `AppPresenceStack` — the faces of the other people here. It sits immediately before syncStatus, after actions and before the divider, because who is here belongs to the view, not to the app's sync. Every floorplan shows presence…
syncStatus Widget? null The sync control — ALWAYS the last inline-end element, after every entry in actions (`AppBar.d.ts` `syncStatus`; the constitution: "the sync/persistence control is a split button and ALWAYS the last inline-end app-bar action, at every window class"). A slot rather than an ordering convention on…
title String? null The view's name — headline role (DesignTokens.headline). Not the app's name; each view supplies its own. Null is the SEARCH-MODE posture and nothing else (`AppBar.d.ts`, where the prop is optional for exactly this): while search is expanded the bar hosts `back · field · syncStatus` and the title…

AppHeaderBarBack widget

The canonical back affordance for AppHeaderBar.leading — a quiet AppIconButton on KitGlyph.back that `maybePop`s its route. Promoted 2026-08-05 from six identical hand-built blocks (two of which had drifted onto Material's `arrow_back`).

Full dartdoc

The canonical back affordance for AppHeaderBar.leading — a quiet AppIconButton on KitGlyph.back that `maybePop`s its route. Promoted 2026-08-05 from six identical hand-built blocks (two of which had drifted onto Material's `arrow_back`). The slot stays a slot: this is an atom the caller drops in, not a `showBack` knob.

AppHeaderBarBackdefault constructor

Knob Type Default What it does
onPressed VoidCallback? null Tap handler; null pops via `Navigator.of(context).maybePop()`.
title String 'Back' Tooltip + semantics label.

AppNavRail widget

The design-system NavRail — the app's left spine, claiming the M3 navigation-rail archetype (with a drawer-hosted overlay form for narrower window classes) — and THE TREE RAIL (`NavRail.d.ts`, #1186): a tree of leaf destinations (AppNavItem) and disclosure groups (AppNavGroup). Three postures share…

Full dartdoc

The design-system NavRail — the app's left spine, claiming the M3 navigation-rail archetype (with a drawer-hosted overlay form for narrower window classes) — and THE TREE RAIL (`NavRail.d.ts`, #1186): a tree of leaf destinations (AppNavItem) and disclosure groups (AppNavGroup). Three postures share one widget and one selection grammar (see AppNavRailPosture): AppNavRailPosture.rail (56, glyphs alone), AppNavRailPosture.extended (256, the tree) and AppNavRailPosture.drawer (the tree at a drawer's width). **THE RAIL'S BODY IS THE GROUND** (`Scaffold.d.ts` ONE ROW, #1186 § 9). It paints nothing of its own — no fill, no rule against the workspace. Its optional pinned head (the host's menu button and brand, `--bar-h` tall) is the one band in it: under `chrome: accent | deep` the head takes the chrome and reads as one band with the view bar beside it, while the tree stays on the plain ground. The footer keeps its DesignTokens.hairS rule: it divides the rail's own regions. **THE FOOTER GROUP** (`Scaffold.d.ts` § 12): behind that rule, top to bottom, the footer DESTINATIONS (pages, which can be lit), the footerActions (commands, never lit), the footerAccount (the signed-in person, opening a menu at itself) and the footerBrand (a logo, never focusable) — each optional, all pinned. **THE SELECTION is Google's pill.** Extended and in the drawer the lit leaf's row carries an DesignTokens.accentSel pill flush to the rail's inline-start edge, fully rounded on its inline-end side and ending 8 short of the rail's inline-end edge; its glyph takes DesignTokens.accentInk, its label DesignTokens.ink (text floor 4.5:1, #1747), the label at 600. Collapsed it is a 40 × 40 circle centered on the glyph. A lit leaf hidden inside a closed group (or any group, collapsed) lends the pill to its nearest VISIBLE ancestor, so the selection is never invisible. Inactive rows: the glyph in DesignTokens.ink60, the label in DesignTokens.ink at 500. The pill's shape is the rail's, never a role radius — the `corners` knob does not touch it. **THE GLYPH IS ANCHORED.** One glyph size (glyphSize, 20) in both postures, and a depth-0 glyph's horizontal center sits glyphAnchor (28) from the rail's inline-start edge in both, so a toggle never moves a glyph sideways. Extended, the row reads: the chevron column (a depth-0 group's 12px chevron centered 9 in), the glyph, the label at labelStart (52), the badge 8 after the label. A child row (depth ≥ 1) is text only, its label at `52 + (depth − 1) × 24`, a child group's chevron centered 12 before it. **Groups are folders.** More than one may be open. The rail holds the open set itself: SEEDED, and RE-SEEDED whenever the lit leaf changes, so every ancestor of the lit leaf is open (ancestors are added; nothing is removed), while the user's own toggles last for the life of the rail. **COLLAPSED RAIL ICON-ONLY.** Each collapsed row's accessible name is its label with the badge's description appended ("Inbox, 3 new"), and it carries the kit AppTooltip at AppTooltipSide.end, its words the label alone (`NavRail.d.ts` ACCESSIBILITY, #1896) — labels hide only here, and only because the host's menu button one tap away restores every label. State contract (all honored) per row: enabled · hover (DesignTokens.groundHover in the pill or circle shape) · focus-visible (1px accent ring on the row's shape) · pressed (1px translateY) · selected (the pill) · Semantics: a leaf is a button, `selected` when lit; a group is a button with `expanded`. Enter and Space activate. The whole rail is a `Semantics(container: true)` region labeled "Navigation". **The destination list scrolls** (`NavRail.d.ts`, #406, simplified by #1186): the items in their own bounded scroller between the pinned head above and the pinned footer group below. The lit row is scrolled into view by the nearest edge after first layout (a settle), whenever selectedIndex changes from outside the rail, after a group auto-opens and across a posture change; a tap never moves the list under the finger. The rail therefore needs a BOUNDED height, which every scaffold host gives it. ```dart AppNavRail( posture: extended ? AppNavRailPosture.extended : AppNavRailPosture.rail, onPostureChange: (p) => setState(() => extended = true), items: const [ AppNavItem(glyph: KitGlyph.house, label: 'Home'), AppNavGroup( glyph: KitGlyph.users, label: 'Directory', children: [ AppNavItem(label: 'Users', badge: AppNavBadge.count(3)), AppNavItem(label: 'Groups'), ], ), ], selectedIndex: selected, // the LEAF index, depth-first onSelect: (i) => setState(() => selected = i), footer: const [AppNavItem(glyph: KitGlyph.gear, label: 'Settings')], ) ```

AppNavRaildefault constructor

Knob Type Default What it does
footer List<AppNavItem>? null Optional LEAVES pinned to the bottom behind a hairline separator (settings/account), the same row anatomy as the list. Null or empty renders no footer group.
footerAccount AppNavFooterAccount? null THE FOOTER ACCOUNT (`NavRail.d.ts`, steady-core #1739): the signed-in person, one row after the footerActions and before the footerBrand — the footer's last interactive row. An AppAvatar (24, mono) in the glyph slot and the AppNavFooterAccount.name as the label; collapsed, the avatar alone, the name…
footerActions List<AppNavFooterAction> const [] The footer group's ACTIONS (`Scaffold.d.ts` § 12): global commands pinned after the footer destinations — the same row anatomy, never lit, outside every index. Empty renders none.
footerBrand AppNavFooterBrand? null The footer group's BRAND (`Scaffold.d.ts` § 12): a logo pinned at the very bottom, below every row — the AppNavFooterBrand.mark extended and in the drawer, its AppNavFooterBrand.compact (or nothing) collapsed. It never scrolls. Null renders none.
footerSelectedIndex int? null Index into footer currently active, or null if no footer destination is selected (the default). Exactly one destination carries the selection at a time, mirroring the spec's single `activeKey` (asserted mutually exclusive with selectedIndex).
head Widget? null The rail's pinned HEAD (`NavRail.d.ts` / `Scaffold.d.ts` ONE ROW, #1186 § 9) — at ≥960 the host's menu button and brand, sized by the host to `--bar-h` so the head and the view bar beside it read as ONE band. It never scrolls. The head is a CHROME HOST: under `chrome: accent | deep` the rail paints…
items * List<AppNavNode> — The primary tree, top to bottom: leaves and groups, at most three levels deep.
onFooterSelect ValueChanged<int>? null Fires with the tapped footer item's index into footer — the spec's one `onSelect` covers both groups; this is its footer half. Null leaves footer rows inert.
onPostureChange ValueChanged<AppNavRailPosture>? null Asked when a GROUP is activated in the collapsed rail — with AppNavRailPosture.extended, and for nothing else: the group is added to the open set so it is open when the rail finishes extending, and keyboard focus stays on its row. Null leaves a collapsed group tap opening the group for the next…
onSelect * ValueChanged<int> — Fires with the tapped leaf's depth-first index. Groups never fire it.
posture AppNavRailPosture
rail · extended · drawer
AppNavRailPosture.rail Layout posture — see AppNavRailPosture. Defaults to the collapsed AppNavRailPosture.rail.
selectedIndex * int? — The active LEAF's index in depth-first order across items (a group contributes its leaves in order), or null if none is selected.

AppRailMark widget

The canonical brand/app mark — the shell's `railMark` (`NavRail.d.ts` `RailMark`): a small accent block carrying one glyph in the on-accent ink — or, via mark, the app's AppBrandMark image, which brings its own paint. **IDENTITY ONLY** (`Scaffold.d.ts` § 11, #1186). The mark says whose app this is…

Full dartdoc

The canonical brand/app mark — the shell's `railMark` (`NavRail.d.ts` `RailMark`): a small accent block carrying one glyph in the on-accent ink — or, via mark, the app's AppBrandMark image, which brings its own paint. **IDENTITY ONLY** (`Scaffold.d.ts` § 11, #1186). The mark says whose app this is and nothing else: it is never a control, never the scope picker (the active scope leads the view bar's subtitle — `AppScaffold.scope`), never the selection grammar and never a badge. Decorative to a screen reader: the rail head's title (or, with the title hidden, the head's own heading node) names the region. Size and radius are component geometry (28px block, 16px glyph, DesignTokens.rS corner); paint is token-only.

AppRailMarkdefault constructor

Knob Type Default What it does
color String? null THE BLOCK'S COLOR (`NavRail.d.ts` `RailMarkProps.color`, steady-core #1928): a palette NAME (`tokens/palette/Palette.d.ts`) the block paints in place of the accent — its `fill` under the glyph in its `on`, at the same box, glyph and corner. Null, or a name DesignTokens.palette does not hold, paints…
glyph KitIconRef? null Design-system stroke glyph. Wins over icon when both are set. Ignored when mark is set.
icon IconData? null A Material glyph, for a consumer that has not moved to glyph yet. Ignored when mark is set.
mark AppBrandMark? null THE BRAND MARK (`Scaffold.d.ts`, #844): the app's image mark, drawn INSTEAD of the glyph — contain-fit inside the 28px block at the block's radius, in the variant the theme calls for (AppBrandMark.forBrightness), with NO accent fill and never tinted: the mark brings its own paint. The glyph is the…

AppBrandMarkImage widget

Draws an AppBrandMark contain-fit inside a width × height box, in the variant the ambient theme's brightness calls for — never tinted (no color or blend is ever applied to the image). semanticLabel names the mark for assistive technology (typically the app's name); null leaves it decorative, which…

Full dartdoc

Draws an AppBrandMark contain-fit inside a width × height box, in the variant the ambient theme's brightness calls for — never tinted (no color or blend is ever applied to the image). semanticLabel names the mark for assistive technology (typically the app's name); null leaves it decorative, which is right wherever a text line beside it already says the name.

AppBrandMarkImagedefault constructor

Knob Type Default What it does
alignment AlignmentGeometry Alignment.center Where the fitted image sits inside the box.
height * double — The box's height — the mark is contain-fit within it.
mark * AppBrandMark — The mark to draw.
semanticLabel String? null The mark's accessible name; null excludes it from semantics.
width double? null The box's width. Null lets the image take its own aspect at height.

AppTabs widget

The design-system Tabs — one start-aligned row of label-width tabs over a hairline rail, the selected one at full ink and weight 600 over an accent underline that overlaps the rail. **THE CONTAINED TAB** (Tabs.d.ts, #893): the underline runs under the LABEL only — inset by `--tab-px` from both…

Full dartdoc

The design-system Tabs — one start-aligned row of label-width tabs over a hairline rail, the selected one at full ink and weight 600 over an accent underline that overlaps the rail. **THE CONTAINED TAB** (Tabs.d.ts, #893): the underline runs under the LABEL only — inset by `--tab-px` from both inline edges of the tab, so the line is exactly the label's width — while the hover wash and the hit region keep the whole tab. Unselected labels are ink60 at 500. Switches between VIEWS of one surface. It never edits a value — that is `AppSegmentGroup`'s job, and the two are not interchangeable. **The underline is a transparent border on every tab**, so selection never changes the row's height and nothing below the row shifts when the selection moves. A pane whose content jumps as the user changes tabs is the defect this anatomy is shaped to prevent. **Overflow scrolls inline** — start-pinned. It never wraps (a second rail under the first stops reading as one tablist) and never ellipsizes a label (a truncated view name is unreadable navigation). Three riders (Tabs.d.ts OVERFLOW, 2026-09-29): each edge the row can still scroll toward FADES its tabs out over `--tab-fade` (an alpha mask; the rail never fades); the selected tab is SCROLLED INTO VIEW after the first layout and on every change of value; and selection never changes a tab's WIDTH — each tab reserves its label's selected (600) width. **inset** pads the scroll content inline, so the first tab can sit on a host's gutter while the rail still spans the whole row. **Keyboard**: arrow keys move the selection to the next/previous non-disabled tab AND select it, wrapping past the ends; focus follows (roving tabindex). Enter/Space select the focused tab. Tabs never un-select — tapping the selected one is a no-op. ```dart AppTabs<String>( semanticLabel: 'View options', value: tab, onChanged: (v) => setState(() => tab = v), options: const [ AppTabOption(value: 'filter', label: 'Filter'), AppTabOption(value: 'group', label: 'Group'), AppTabOption(value: 'columns', label: 'Columns', count: 4), ], ) ```

AppTabsdefault constructor

Knob Type Default What it does
inset double 0 The row's inline inset in logical px: the scroll content's padding at both ends, so the first tab starts inset from the inline-start edge while the rail spans the whole row (Tabs.d.ts INSET). The dashboard's board row passes the plate's padding, putting the first tab on the tiles' edge. Default 0.
onChanged ValueChanged<T>? null Receives the tapped tab's value. Null disables every tab.
options * List<AppTabOption<T>> — The views this row switches between, in display order. No upper bound — the row scrolls rather than wrapping.
semanticLabel String? null Names the tablist for assistive tech. There is deliberately no visible label parameter: a micro-label above a tab row is a field affordance, and the tabs' own labels are the naming.
value T? null The selected tab's value, or null if none is selected yet. A value naming no option selects NO tab (and warns in debug builds); the row never invents a selection.
variant AppTabsVariant
underline · folder
AppTabsVariant.underline Paint variant — see AppTabsVariant.

Types its knobs take

These are not things you choose — they are the shapes the knobs above expect. They live under this entry rather than in the navigation for exactly that reason.

AppBrandMark support

A shell's brand mark — one image in two variants (`Scaffold.d.ts` THE BRAND MARK). positive is drawn on a light surface; knockout, when supplied, on a dark one. Absent, positive serves both themes.

Field Type Default What it does
knockout ImageProvider? null The mark for a DARK surface. Null uses positive.
positive * ImageProvider — The mark for a LIGHT surface.

AppMenuHeading support

THE HEADING of an AppMenu (`Menu.d.ts` `MenuHeading`, steady-core #1739): what the menu's verbs act on, said once above them — the account item's heading is the signed-in person, their name over their email. A statement, never a verb: anything the person can act on is an AppMenuItem.

Field Type Default What it does
detail String? null One line under title in DesignTokens.label at DesignTokens.ink60, ellipsized at the panel's width: the menu's description. Null or empty draws no second line.
title * String — One line in DesignTokens.label at 600 DesignTokens.ink, ellipsized at the panel's width: the menu's name.

AppMenuItem support

One selectable row in an AppMenu's popover. Mirrors `MenuItemSpec` (Menu.d.ts): a label with an optional leading glyph, a checked current-item ✓, a destructive danger paint, a hairline dividerBefore separator, enabled gating, and a supporting line under the label.

Field Type Default What it does
checked bool? null The item's CHECKABLE state, or null for a plain item that has none. Mirrors `MenuItemSpec.checked` (Menu.d.ts), which is optional for a reason the Dart type has to carry too: the ✓ is the VISUAL half of a fact assistive tech has to hear, so a checked item is a *checkable* item — it reports its state…
danger bool false True paints the row in DesignTokens.danger — a destructive action (e.g. "Delete view"). Never a red slab, ink only (constitution: encode once).
dividerBefore bool false True renders a hairline Divider immediately above this item — grouping separator (e.g. before a destructive item).
enabled bool true Disables the row (DesignTokens.ink40 foreground, not tappable) when false, independent of onSelected.
glyph KitIconRef? null Optional 16px leading design-system glyph (Menu.prompt.md: "16px stroke icon, optional"). Wins over icon when both are set.
glyphColor String? null THE TINTED SQUARE (`Menu.d.ts`, steady-core #1928): a palette NAME (`tokens/palette/Palette.d.ts`) the leading glyph is drawn in — a 24 square at the DesignTokens.rS corner, its plate the name's KitPaletteColor.tint, the 16 glyph centered in its KitPaletteColor.ink. Its first consumer is the app…
icon IconData? null A Material glyph, for a consumer that has not moved to glyph yet.
label * String — The row's visible text, rendered in DesignTokens.body.
navigates bool false THE ACTION THAT ONLY OPENS A PAGE (`ActionStrip.d.ts`, steady-core #1673): choosing it only opens another screen and writes nothing, so the row also reports a link (`Semantics(link: true)`, merged into the row's node). No paint change. Defaults to false.
onSelected VoidCallback? null Fires when the item is chosen. The menu closes first, then this fires — null (or enabled false) renders the row disabled and not tappable.
supporting String? null THE SUPPORTING LINE (Menu.d.ts `MenuItemSpec.supporting`, 2026-10-11): one line under the label in DesignTokens.label at DesignTokens.ink60 — the M3 list item's supporting text at a menu's scale. It says WHY the item is the way it is ("Needs a connection" under a disabled file action); never a…

AppNavFooterAccount support

THE FOOTER ACCOUNT (`NavRail.d.ts`, `Scaffold.d.ts` THE ACCOUNT ITEM, steady-core #1739) — the signed-in person at the foot of an AppNavRail's footer: how a person sees who they are signed in as and reaches the verbs about their session (Sign out first among them). Pure data, and the kit never reads…

Field Type Default What it does
detail String? null One line under the name in the opened menu's heading (an email). Never drawn on the row. Null or empty draws nothing.
items * List<AppMenuItem> — The menu's verbs, in order.
name * String — The person's name: the row's label, the collapsed row's accessible name and tooltip, the avatar's initials and the menu heading's title.

AppNavFooterAction support

A FOOTER ACTION in an AppNavRail (`NavRail.d.ts` THE FOOTER GROUP, `Scaffold.d.ts` § 12, #1186) — a global command with no body ("Send feedback", "Help"). The same 40 row anatomy as a destination, but NEVER lit and outside the destination index: activating it runs onSelect and moves no selection.

Field Type Default What it does
badge AppNavBadge? null The row's badge (§ 5), or null.
glyph KitIconRef? null Design-system stroke glyph (20px). Wins over icon.
icon IconData? null A Material glyph, for a consumer that has not moved to glyph yet.
key String? null An optional stable identity for the row.
label * String — The row's label — the accessible name and the tooltip collapsed.
onSelect * VoidCallback — Runs the command.

AppNavFooterBrand support

THE FOOTER BRAND (`NavRail.d.ts` THE FOOTER GROUP, `Scaffold.d.ts` § 12, #1186) — an organization's or partner's logo pinned at the very bottom of the rail and the drawer: identity, not navigation. Not focusable, not interactive; it reads to a screen reader as one image named label.

Field Type Default What it does
compact Widget? null The square logo the COLLAPSED rail draws, centered on the glyph line at most 28 square. Null draws nothing collapsed.
label * String — The logo's accessible name.
mark * Widget — The logo, drawn EXTENDED and in the drawer: contained within the rail's width less `--rail-pad` each side and at most 28 tall, centered.

AppNavItem support

A LEAF destination in an AppNavRail (or its AppNavRail.footer). Mirrors `NavRailItem` in `design-system/components/navigation/NavRail.d.ts`.

Field Type Default What it does
badge AppNavBadge? null
danger bool false Marks the item as a danger destination (e.g. "Blocked" / "Issues"). Paints the glyph + label in DesignTokens.danger instead of the normal ink/accent pair, active or not.
glyph KitIconRef? null
icon IconData? null
label * String —

AppNavNode support

One node of an AppNavRail's tree (`NavRail.d.ts` `NavRailNode`): a leaf destination (AppNavItem) or a folder of nodes (AppNavGroup). Pure data — no widget refs, no callbacks.

Field Type Default What it does
badge AppNavBadge? null The row's badge (§ 5), or null. A GROUP with none of its own shows the roll-up dot while any descendant carries one and its children are not on screen.
glyph KitIconRef? null Design-system stroke glyph (20px in every posture). Wins over icon. Required at depth 0; ignored below.
icon IconData? null A Material glyph, for a consumer that has not moved to glyph yet.
key String? null An optional stable identity. A group's open state is remembered by it (else by the node's position in the tree), so a host that inserts or reorders nodes keeps the user's open groups open.
label * String — The row's label — on one line with an ellipsis when extended, the accessible name and the tooltip when collapsed.

AppScaffoldApps support

THE APP SWITCHER's value (`Scaffold.d.ts`, steady-core #1841): the apps this person can open, the one this scaffold belongs to, and where choosing goes. The brand draws it only with two or more apps.

Field Type Default What it does
apps * List<AppScaffoldApp> — The openable apps, in the host's order (never recency).
current * String — The id of the app this scaffold belongs to; checked in the menu.
mark AppSwitcherMark AppSwitcherMark.brand What the closed button draws as its mark (AppSwitcherMark): the brand by default, or the current app's glyph.
onAllApps VoidCallback? null The launcher, as the menu's last row ("All apps"); null omits it.
onSelect * ValueChanged<String> — Choosing another app. Never called for current.

AppScaffoldDestination support

One LEAF destination hosted by AppScaffold.destinations — a body, its header slots and its rail row. Pure data — no widget refs beyond builder itself.

Field Type Default What it does
actions List<Widget> const [] Trailing app-bar actions surfaced while this destination is active, rendered before AppScaffold.actions and, in turn, before AppScaffold.syncStatus — the destination's own controls sit closest to the title, the scaffold-level actions next, the sync control always last. Empty (the default) adds…
badge AppNavBadge? null The destination's badge on its nav row, bar slot or dock item (`NavRail.d.ts` § 5) — a count, a text pill or a dot. Null renders none.
builder * WidgetBuilder — Builds the body shown when this destination is active. Every destination's builder runs once into the scaffold's IndexedStack and is kept alive (state + scroll offset survive switches) for the rest of the session — the same build-once-keep-alive contract `AppShell` uses. builder is a plain…
glyph KitIconRef? null Design-system glyph shown in the rail / drawer entry. Wins over icon when both are set. Required at depth 0; ignored below.
headerPosture AppHeaderPosture AppHeaderPosture.standard How this destination's header rests on a phone (THE COMPACT POSTURES, #1103): AppHeaderPosture.hero takes the hero band and the rising sheet below 600 for a records destination. Medium and wider always take the bar.
icon IconData? null A Material glyph, for a consumer that has not moved to glyph yet.
key String? null An optional STABLE identity (default: the destination's leaf index). It keys the destination's kept-alive body and its rail row, so a host that inserts a destination ahead of it keeps this body's state.
label * String — User-visible label.
meta String? null Optional data-font annotation shown beside the app-bar title while this destination is active — a record count ("128 of 128"), a last-synced time. Flows into AppHeaderBar.meta (rendered in DesignTokens.dataS, the data role). Null renders no meta text.
presence Widget? null THE DESTINATION'S PRESENCE (`Scaffold.d.ts` `ScaffoldDestination.presence`, steady-core #1331): the `AppPresenceStack` of the scope this destination's view joins, drawn in the bar's presence slot while the destination is active — immediately before sync, in the standard bar and the hero band's row…
primaryAction ActionSpec? null THE view's single loud action — the `primary` tier of the action canon, hosted by the scaffold so every screen gets the same posture without re-implementing it: an AppFab in the thumb zone at **every** window class (D-N4, 2026-08-16 — the medium/expanded bar slab is retired), `extended` at ≥960 so…
title String? null Optional app-bar title shown while this destination is active. Null falls back to AppScaffold.title — keeps single-destination scaffolds and scaffolds that share one title across every destination from needing to repeat it.

AppScaffoldDrawerAction support

One row in AppScaffold's overlay-drawer "global actions" section — sign-out, about, etc. Distinct from AppScaffoldDestination: destinations are primary nav, drawer actions are global commands that never carry a body — which is why Settings is not one: it has a body, so it is an…

Field Type Default What it does
glyph KitIconRef? null Design-system glyph shown on the leading side of the row. Wins over icon when both are set.
icon IconData? null A Material glyph, for a consumer that has not moved to glyph yet.
label * String — User-visible label.
onPressed * VoidCallback — Fires after the drawer dismisses.

AppScaffoldNavNode support

A node of AppScaffold.destinations' navigation tree (`Scaffold.d.ts`, THE TREE RAIL, #1186): a LEAF destination (AppScaffoldDestination) or a FOLDER of nodes (AppScaffoldDestinationGroup). Leaves carry bodies; groups only organize them.

AppScaffoldScope support

The active SCOPE — which slice of the data the user is working in — of whatever KIND the app declares (Project, Organization, Site, Crew…). `AppScaffold.scope` states it as the FIRST item on the view bar's subtitle line, before the destination's meta (`North Plant ⌄ · 8 records`), at every window…

Field Type Default What it does
kind * String — What KIND of scope this is ("Project", "Organization") — said in the accessible name and its tooltip, never drawn in the bar.
label * String — The active scope's own name ("North Plant") — the text the bar draws.
onSelect void Function(BuildContext anchor)? null Opens the scope picker, handed the scope item's OWN BuildContext as the `anchor` it drops from (`showAppRefPicker(anchor, …)`) — on a phone, the title block's, and the picker then presents as the bottom sheet (`Select.d.ts` THE PRESENTATION RULE). Null states the scope as plain text, not a control.
parent String? null What the scope sits under ("Northwind") — NEVER drawn: it joins the accessible name, `Project: North Plant (Northwind)`, and is the host's picker's to use as a group header.
tooltip String? null The host's own words for the item, in place of `kind: label (parent)`: its accessible name AND its tooltip, one string (`AppBar.d.ts` THE SCOPE ITEM's `title`, #1950).

AppTabOption support

One tab in an AppTabs (`TabsOption`, Tabs.d.ts): a value, its label, a per-tab disabled flag, and an optional count. A class rather than a record (a record until 2026-10-11): a record cannot grow an optional member without breaking every literal, and THE COUNT was the second member a tab needed.

Field Type Default What it does
count int? null THE COUNT (Tabs.d.ts `TabsOption.count`, 2026-10-11): how many things the tab's view holds ("Comments 3", ReaderScreen.d.ts MARKUP), drawn after the label in the DATA role — DesignTokens.dataS at DesignTokens.ink60 whatever the selection, never the selected weight — 6px (`--tab-count-gap`) after it.…
disabled bool false True renders this tab disabled (DesignTokens.ink40, not selectable, skipped by the arrow keys).
label * String — The view's name — the tab's visible label and the start of its accessible name. Never ellipsized: the row scrolls instead.
value * T — The value AppTabs.value selects and AppTabs.onChanged receives.

KitFabDeckEdge support

A DECKED record's live edge, in GLOBAL logical px — what the scaffold's Fab rides below 960 (`RecordsScreen.d.ts` THE FAB RIDES THE DECK, 2026-10-01, #963).

Field Type Default What it does
room * double — The height of the records region left ABOVE the deck. A region shorter than the Fab's block plus its inset twice (a phone in landscape) holds no Fab.
standsDown * bool — Whether the Fab stands down outright: the deck stands above MID (it covers the collection), or the floorplan's `fabBesidePane` is false.
top * double — The deck's live top edge, as a global y. The Fab's usual inset is measured UP from here instead of from the window's bottom.

KitIconRef support

A glyph, by NAME — what every glyph-bearing kit API takes (a pin, a legend key, a nav item, an action, an empty face, a category card). Two shapes implement it: - a KitGlyph — a glyph in the kit's own name table, checked by the compiler (`KitGlyph.syringe`); - KitIconRef.named — any name as a…

Field Type Default What it does
name * String —

Where it lives

Class File
AppScaffold packages/app_widgets/lib/src/kit/surfaces/scaffold.dart
AppHeaderActions packages/app_widgets/lib/src/kit/surfaces/scaffold.dart
AppHeaderBand packages/app_widgets/lib/src/kit/surfaces/scaffold.dart
AppHeaderHostBoundary packages/app_widgets/lib/src/kit/surfaces/scaffold.dart
KitSheet packages/app_widgets/lib/src/kit/surfaces/sheet.dart
AppDialog packages/app_widgets/lib/src/kit/surfaces/dialog.dart
AppMenu packages/app_widgets/lib/src/kit/surfaces/menu.dart
AppOverlayPanel packages/app_widgets/lib/src/kit/surfaces/overlay_panel.dart
AppTooltip packages/app_widgets/lib/src/kit/surfaces/tooltip.dart
AppHeaderBar packages/app_widgets/lib/src/kit/navigation/header_bar.dart
AppHeaderBarBack packages/app_widgets/lib/src/kit/navigation/header_bar.dart
AppNavRail packages/app_widgets/lib/src/kit/navigation/nav_rail.dart
AppRailMark packages/app_widgets/lib/src/kit/navigation/nav_rail.dart
AppBrandMarkImage packages/app_widgets/lib/src/kit/navigation/brand_mark.dart
AppTabs packages/app_widgets/lib/src/kit/navigation/tabs.dart