Status and feedback
What a surface says when it is not simply showing data: banners, status pills and status marks, the waiting states, the two empty states that are not the same state, and the bulk bar.
Spec of record: design-system/components/feedback/Banner.d.ts
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.
Every specimen at once, in one state. Per-specimen controls would rebuild the wall a board exists to remove.
Dart for these values
family_status() 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.
AppBanner widget
The design-system Banner — a persistent, inline notice about the surface it stands with (offline, sync failed, stale data, a blocked record). **Not a floating toast.** Toasts do not exist in this system; a transient confirmation (e.g. "SR-1042 deleted") belongs in the Modeline, never here.
Full dartdoc
The design-system Banner — a persistent, inline notice about the surface it stands with (offline, sync failed, stale data, a blocked record). **Not a floating toast.** Toasts do not exist in this system; a transient confirmation (e.g. "SR-1042 deleted") belongs in the Modeline, never here. AppBanner sits inline, above scroll, describing a *persistent* condition. WHERE it stands is its host's clause (`RecordsScreen.d.ts` THE BANNER'S RANK AGAINST THE FACES, `DetailView.d.ts` THE SURFACE NOTICE IS THIS COMPONENT'S SLOT); a host that used to bleed it across a surface insets it to that surface's padding. THE CONTAINED BANNER (`Banner.d.ts`, 2026-09-29, #896) is a TINTED PLATE: the tone's fill at DesignTokens.rS corners, padded 10 × 12 and sized by its content, with no border, hairline or shadow (the tint alone separates it from the card or ground beneath). A leading 16 glyph — KitGlyph.warning by default for AppBannerTone.danger, none by default for AppBannerTone.info — stands 10 before the message on its FIRST line. The message wraps and is never truncated (12.5 at the body line height, weight 400); an optional lead opens it at the em weight ("**Blocked since Sep 22.** Check valve 2 failed its test."). The meta, the one ghost action and the dismiss "✕" trail at the plate's inline end, aligned to its top. The 2px start edge, the strip's bottom hairline and the 40 minimum height are DELETED. Both tones are live regions (`Semantics(liveRegion: true)`) so assistive tech announces the notice as it appears. ```dart AppBanner( message: 'Working offline. Changes sync on reconnect.', glyph: KitGlyph.cloudOff, ) AppBanner( tone: AppBannerTone.danger, lead: 'Blocked since Sep 22.', message: 'Check valve 2 failed its test.', actionLabel: 'Retry', onAction: retry, ) ```
AppBannerdefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| actionLabel | String? | null | Label for the single trailing action, rendered as a ghost AppButton. Renders only when onAction is also non-null. |
| dismissLabel | String | 'Dismiss' | Tooltip and accessible name for the dismiss "✕". Ignored when onDismiss is null. |
| glyph | KitIconRef? | null | The leading 16 glyph. Null takes the tone's default: KitGlyph.warning for AppBannerTone.danger, none for AppBannerTone.info — an operational notice names its condition's glyph (KitGlyph.cloudOff offline, KitGlyph.sync a sync fault, KitGlyph.clock stale data), or starts on its text. |
| lead | String? | null | The lead run that opens the message at the em weight ("Blocked.", "Blocked since Sep 22.") — the intra-sentence run of the Anatomy rules. Null or blank renders no lead. |
| message * | String | — | The condition, stated factually — cause + consequence, no exclamation marks (e.g. "Working offline. Changes sync on reconnect."). |
| meta | String? | null | Quiet data-font count/timestamp beside the message (`Banner.d.ts` `meta`): "3 pending", "synced 09:41". Machine text — renders in DesignTokens.dataS in the plate's ink, never restated in the message prose. |
| onAction | VoidCallback? | null | Callback for the single trailing action. Renders only when actionLabel is also non-null. |
| onDismiss | VoidCallback? | null | Fires when the dismiss "✕" is tapped. Null omits the dismiss control entirely — a live failure a caller wants to keep visible simply omits this, rather than rendering a disabled "✕". |
| tone | AppBannerTone info · danger | AppBannerTone.info | Closed tone set — AppBannerTone.info (operational notice) or AppBannerTone.danger (failed state demanding action). Governs the plate's fill and ink, and the default glyph. |
AppStatusPill widget
The design-system StatusPill — a tinted plate carrying a short word or figure (+ optional leading 12px glyph) in the tone's ink. For LIVE, transient, or derived state only.
Full dartdoc
The design-system StatusPill — a tinted plate carrying a short word or figure (+ optional leading 12px glyph) in the tone's ink. For LIVE, transient, or derived state only. A stored status stays a word in its cell; this widget never re-encodes a value. ```dart AppStatusPill(tone: StatusTone.caution, label: 'Syncing…') AppStatusPill( tone: StatusTone.positive, glyph: KitGlyph.trendUp, data: true, label: '+12%', ) ```
AppStatusPilldefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| data | bool | false | Render label in the small data role (counts, deltas — machine text). |
| glyph | KitIconRef? | null | Leading design-system glyph, 12px, in the tone's ink. Wins over icon. |
| icon | IconData? | null | A Material glyph for a consumer not yet on glyph. |
| label * | String | — | Short word or figure. |
| tone | StatusTone positive · info · caution · critical · neutral | StatusTone.neutral | The annotation tone. Defaults to StatusTone.neutral. |
AppStatusMark widget
The design-system StatusMark — a stored status as its WORD beside an 8px square marker (DesignTokens.rMark), or, for AppStatusMarkTone.blocked, the filled danger tag. The word renders in DesignTokens.ink60, the UI font at the HOST's size: style is the host's text style, and the word takes its size…
Full dartdoc
The design-system StatusMark — a stored status as its WORD beside an 8px square marker (DesignTokens.rMark), or, for AppStatusMarkTone.blocked, the filled danger tag. The word renders in DesignTokens.ink60, the UI font at the HOST's size: style is the host's text style, and the word takes its size (and line height) — a table cell passes nothing and the word reads at DesignTokens.body, the cell's own size; a larger host passes its value style. Color and family are the mark's, never the host's. The blocked tag is always the label role (12/500), whatever the host. Semantics: ONE node reading the word alone. The marker is excluded — it is the word's glyph, and a reader hearing "square" before every status would be hearing decoration. ```dart AppStatusMark(label: 'In progress', tone: AppStatusMarkTone.active) AppStatusMark(label: 'Blocked', tone: AppStatusMarkTone.blocked) ```
AppStatusMarkdefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| label * | String | — | The stored value's human word — always rendered; it carries the meaning. |
| style | TextStyle? | null | The host's text style, whose SIZE the word takes (`StatusMark.d.ts`: "the UI font at the host's size"). Null reads DesignTokens.body. Ignored by AppStatusMarkTone.blocked, whose tag is the label role. |
| tone * | AppStatusMarkTone pending · active · done · blocked | — | Which of the four tones the host mapped the value onto. |
AppProgress widget
The design-system Progress — the live-operation loader. An optional accent micro-label (the state word — "generating", "queued") over a 2px indeterminate track: a 28%-wide DesignTokens.accent bar sweeping a DesignTokens.accentTint rail, one sweep per DesignTokens.tMove × 5.75 (1.15s in graphite),…
Full dartdoc
The design-system Progress — the live-operation loader. An optional accent micro-label (the state word — "generating", "queued") over a 2px indeterminate track: a 28%-wide DesignTokens.accent bar sweeping a DesignTokens.accentTint rail, one sweep per DesignTokens.tMove × 5.75 (1.15s in graphite), easing with DesignTokens.easeIn. A record mid-operation server-side renders this in its row's status slot AND its detail's build state — the same treatment both places, so "building" reads identically everywhere. Division of labor (the spec states it once): content whose SHAPE is known but not yet fetched is `AppSkeleton`; a live operation the user is watching is AppProgress; never a spinner in either seat. Indeterminate only — a determinate percentage stays host anatomy (a table's progress cell, a detail fact's inline bar), not a knob here. Under reduced motion (reducedMotionOf — the framework flag OR the platform's own Reduce Motion) the bar rests static at the inline start and no AnimationController is ever created; under RTL the sweep mirrors (start → end is a reading direction). The sweep is a LOOP period, so it keeps the stock DesignTokens.tMove multiple at the desk. The track is decorative to assistive tech; the widget announces label through its own `Semantics` node when one is given. ```dart const AppProgress(label: 'generating') const AppProgress(label: 'queued') const AppProgress() // track alone — the word is carried elsewhere ```
AppProgressdefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| label | String? | null | The state word, rendered as the accent micro-label above the track. Null (or empty) renders the track alone — the posture for a host that already states the word beside it. |
SyncStatusButton widget
App-bar sync control. A split pill: the main zone fires a one-tap manual sync (and is the retry affordance when a cycle failed); the trailing caret zone opens the detail panel.
Full dartdoc
App-bar sync control. A split pill: the main zone fires a one-tap manual sync (and is the retry affordance when a cycle failed); the trailing caret zone opens the detail panel. The main zone is enabled in EVERY phase, `syncing` included: a tap during an in-flight cycle is the user's request that the engine promotes to a visible, spinning cycle, so onSyncNow must tolerate being called while a cycle runs (the sync worker's `runOnceFromUser` does — it promotes and queues, never double-runs). **Icon-only at every window class** (`design-system/components/navigation/ AppBar.prompt.md`, pin `839c92d`), and it **rests on the sync glyph** — the idle-with-nothing-queued state is not a ✓. Both were labeled/checked until 2026-08-12: a verb beside the glyph competed with the bar's one labeled control (the slab primary) and made the pill's width jump between window classes, and a checkmark reads as a status readout, which is not something a field user taps — so the force-a-sync affordance vanished exactly when the queue was empty. Up-to-date is stated by the tooltip and the absent badge. `offline` and `failed` still swap the glyph: those are conditions, not a completed job. Drops into a scaffold's header-bar action slot. Consumes plain streams plus an onSyncNow callback, so it carries no dependency on the sync engine. Optionally takes onCancelOne / onDiscardAll / onEvacuateOne callbacks for the detail panel's row-resolution affordances; each is independently null-able, and an unwired one renders no control at all rather than a disabled one.
SyncStatusButtondefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| offline | SyncOfflineLine? | null | THE OFFLINE LINE (`SyncStatus.d.ts`, steady-core #1920): the app on screen and its offline readiness, which the detail panel states under its status sentence, and keeps live while it is open. The host passes it only inside an app it keeps ready offline; null draws no line. |
| onCancelOne | Future<void> Function(String intentId)? | null | Optional: cancel a single queued change by intent id. When non-null, the detail panel renders a Cancel-X on each row. |
| onDiscardAll | Future<void> Function()? | null | Optional: cancel every queued change. When non-null AND there are pending changes, the detail panel footer shows a destructive "Discard all" button. |
| onEvacuateOne | Future<void> Function(String intentId, {String? note})? | null | Optional: send a REJECTED or STUCK change to the team and give up on it locally. When non-null, the detail panel renders the labeled send-to-support action on problem rows only (failed or stuck — never plain queued). Wire it to `PendingChangesController.evacuateOne`, which ships the intent before it… |
| onSyncNow * | VoidCallback | — | Invoked when the user taps the main zone to sync now. |
| pendingChanges * | Stream<List<PendingChange>> | — | Stream of the queued local changes awaiting sync. Listened again on every mount, like syncState. |
| syncState * | Stream<SyncState> | — | Stream of the sync engine's condition. Both streams are LISTENED AGAIN on every mount, and this control is remounted without its host rebuilding: `AppScaffold` re-parents its bar when the window crosses the expanded class. So each must take a second listen — `SyncWorker.syncState`, a drift… |
AppEmptyState widget
The design-system EmptyState — a centered "nothing here" surface stating what the view would show plus exactly one way out. Anatomy (per `EmptyState.d.ts` / `.prompt.md`): a title (a flat fact, e.g.
Full dartdoc
The design-system EmptyState — a centered "nothing here" surface stating what the view would show plus exactly one way out. Anatomy (per `EmptyState.d.ts` / `.prompt.md`): a title (a flat fact, e.g. "No records match." — never an apology), an optional one-line message (cause or consequence, e.g. "4 filters applied." — never decoration), and an optional single actionLabel + onAction pair rendered as one ghost/outline AppButton (the primary action of the empty surface; the accent slab belongs to the app bar, never here). **No illustration, no decorative icon** — the constitution bans both for this surface, and the spec's Flutter mapping calls this out explicitly: "no `Illustration` slot exists on purpose." There is no icon slot on this widget at all. Centers itself in whatever space its parent gives it (typically the body of a list/table/detail view once loading resolves to zero rows). Adaptation strategy: **scale** (density only — the same anatomy at every M3 window class). ```dart AppEmptyState( title: 'No records match.', message: '4 filters applied.', actionLabel: 'Clear filters', onAction: clearFilters, ) const AppEmptyState(title: 'No photos on this record.') ```
AppEmptyStatedefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| action | Widget? | null | The ONE action as a slot (`EmptyState.d.ts` `action` — "ONE action (ghost/outline Button)"), for a caller whose action is not the default ghost: a related detail section's accent outline "+ Add" (`DetailView.d.ts`, THE EMPTY FACE CARRIES ITS ONE ACTION). Renders in the action position; null (the… |
| actionLabel | String? | null | Label for the single action button. Renders only when both actionLabel and onAction are non-null. |
| actionNavigates | bool | false | THE ACTION THAT ONLY OPENS A PAGE (`ActionStrip.d.ts`, #1744): the actionLabel / onAction button pushes another screen and writes nothing, so it reports a link to assistive technology. Declared, never inferred from onAction. A caller passing action sets `navigates` on its own button instead. |
| message | String? | null | One factual line of cause or consequence — e.g. "4 filters applied." Null renders no second line. Rendered as DesignTokens.body in DesignTokens.ink60 (the web `.fb-empty__detail` role). |
| onAction | VoidCallback? | null | Callback for the single action button. Renders only when both actionLabel and onAction are non-null. |
| title * | String | — | What this view shows, stated flat — e.g. "No records match." Rendered as DesignTokens.bodyEm (title text emphasized, per the web `.fb-empty__title` role: body size, weight 600). Never phrased as an apology. |
AppRouteFace widget
The kit's face for a router with no screen to show: the cause stated flat and one way out, to the start location. A whole route surface: it brings its own Scaffold, because a router's error page is pushed with no chrome above it.
Full dartdoc
The kit's face for a router with no screen to show: the cause stated flat and one way out, to the start location. A whole route surface: it brings its own Scaffold, because a router's error page is pushed with no chrome above it. ```dart GoRouter( routes: ..., errorBuilder: (context, state) => AppRouteFace( reason: AppRouteFaceReason.notFound, location: state.uri.path, onHome: () => context.go('/'), ), ); ```
AppRouteFacedefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| location | String? | null | The path that matched nothing, stated under the not-found title so the reader sees what they asked for. Pass the PATH only, never the query: a query can carry a token. Ignored when the navigation failed, because the path is not the cause there. |
| onHome * | VoidCallback | — | Navigates to the start location. The face's ONE action. |
| reason * | AppRouteFaceReason notFound · failed | — | Which of the two faces to show. |
AppSkeleton widget
The design-system Skeleton — a structural loading placeholder. Renders a flat block filled with DesignTokens.ink4, radius DesignTokens.rS (a true circle for AppSkeletonVariant.circle), in the shape of the content that will load. **Never a spinner** — the constitution's "loading = structural…
Full dartdoc
The design-system Skeleton — a structural loading placeholder. Renders a flat block filled with DesignTokens.ink4, radius DesignTokens.rS (a true circle for AppSkeletonVariant.circle), in the shape of the content that will load. **Never a spinner** — the constitution's "loading = structural placeholders" rule: compose several AppSkeletons (or AppSkeletonLine for a text-line stack) in the real layout's geometry so the loading state previews the coming content. Carries a quiet opacity pulse (1.0 → 0.45, looping, easing via DesignTokens.easeIn / DesignTokens.easeOut) — no shimmer sweep, no stagger. The pulse is **disabled under reduced motion** (reducedMotionOf — the framework flag OR the platform's own Reduce Motion); the block renders static (opacity 1.0) instead. The pulse is a LOOP period, so it keeps the stock DesignTokens.tMove multiple at the desk rather than riding the desk's tighter band. The widget is `ExcludeSemantics`d — it is decorative chrome, not content; wrap the loading region itself in a live-region `Semantics` at the call site if an announcement is wanted. ```dart // A detail-view hero placeholder. const AppSkeleton(variant: AppSkeletonVariant.block, width: 320, height: 160) // An avatar placeholder. const AppSkeleton(variant: AppSkeletonVariant.circle, width: 40, height: 40) // A text-line stack (see AppSkeletonLine for the convenience wrapper). const AppSkeleton(width: 120) ```
AppSkeletondefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| height | double? | null | Placeholder height. Defaults to kSkeletonLineHeight for AppSkeletonVariant.line and AppSkeletonVariant.circle; required in practice for AppSkeletonVariant.block since it has no default. |
| variant | AppSkeletonVariant line · block · circle | AppSkeletonVariant.line | Shape variant — governs geometry defaults. Defaults to AppSkeletonVariant.line. |
| width | double? | null | Placeholder width. Null fills the parent's available width (the common case for a text-line placeholder in a `Column`); required in practice for AppSkeletonVariant.block since it has no default. |
AppSkeletonLine widget
Convenience wrapper: a stack of AppSkeletonLine.lines text-line placeholders, gapped by kSkeletonLineGap, the last line rendered at 60% width (mirrors the web Skeleton's `lines` prop — a paragraph tapering to its final line).
Full dartdoc
Convenience wrapper: a stack of AppSkeletonLine.lines text-line placeholders, gapped by kSkeletonLineGap, the last line rendered at 60% width (mirrors the web Skeleton's `lines` prop — a paragraph tapering to its final line). ```dart const AppSkeletonLine(lines: 3) ```
AppSkeletonLinedefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| lines | int | 1 | Number of stacked lines. The final line renders at 60% width regardless of width so the stack reads as a tapering paragraph. Must be >= 1. |
| width | double? | null | Width applied to every line. Null fills the parent's available width. |
AppBulkBar widget
The design-system BulkBar — a contextual bar that appears while rows are selected, replacing (or docking above) the surface it describes. Styled AS a selection object, per the constitution's selection grammar: a 2px DesignTokens.edge-wide accent start-edge spanning the bar's full height, over a…
Full dartdoc
The design-system BulkBar — a contextual bar that appears while rows are selected, replacing (or docking above) the surface it describes. Styled AS a selection object, per the constitution's selection grammar: a 2px DesignTokens.edge-wide accent start-edge spanning the bar's full height, over a DesignTokens.rowSel tint wash — the same encoding a selected table row carries, so the bar reads as "this IS the selection," not a floating toolbar. Anatomy, leading to trailing: a leading ✕ (AppIconButton) that clears the selection (onExit), the selection count in the data font at em weight (DesignTokens.dataEm) plus a noun label, a flexible spacer, and the caller-supplied actions — **outline (or ghost) `AppButton`s only**. There is never an accent slab here: the bar already carries the one accent statement (the start edge), and bulk operations are peers, not a single primary action. A destructive bulk action (e.g. Delete) is a danger-tone outline button; the caller confirms or offers undo, this widget never gates the tap itself. AppBulkBar renders unconditionally for whatever count it's given — the "hide at zero" rule from the spec is the **caller's** concern (a host simply stops building this widget when its selection empties), matching the Widget-slot composition policy: no `showX` boolean paired with a value prop. Adaptation strategy: **scale** (density only — the same anatomy at every M3 window class; on compact widths the actions cluster wraps rather than truncating, same as `AppButton` rows elsewhere in the kit). The bar is a live region (`Semantics(liveRegion: true)`) so assistive tech announces the selection count as it changes. ```dart AppBulkBar( count: 3, onExit: () => setState(() => selection.clear()), actions: [ AppButton(variant: AppButtonVariant.outline, label: 'Assign', onPressed: assign), AppButton(variant: AppButtonVariant.outline, label: 'Export', onPressed: export), AppButton( variant: AppButtonVariant.outline, tone: AppButtonTone.danger, label: 'Delete', onPressed: delete, ), ], ) ```
AppBulkBardefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| actions | List<Widget> | const [] | The bulk-operation actions, supplied by the caller as already-built widgets — an `AppButton` per action, **outline or ghost only**. Never a `slab`: the bar already carries the surface's one accent statement (the selection start edge), and bulk operations are peers. Empty renders no trailing cluster. |
| count * | int | — | Selection size, rendered in the data font at em weight (DesignTokens.dataEm/DesignTokens.ink). |
| exitLabel | String | 'Clear selection' | Tooltip and accessible name for the leading ✕. |
| noun | String | 'selected' | The noun following count (e.g. `'selected'`, `'records selected'`), rendered in DesignTokens.body/DesignTokens.ink60. |
| onExit * | VoidCallback | — | Callback for the leading ✕ that clears the selection. The bar itself holds no selection state — this is the only way selection mode exits from within the bar. |
AppModeline widget
The design-system Modeline — the estate's TRANSIENT message band (`Modeline.d.ts`, transient-only since 2026-08-24 Phase D). A thin hairline strip locked at the `--mode-h` 28px component height.
Full dartdoc
The design-system Modeline — the estate's TRANSIENT message band (`Modeline.d.ts`, transient-only since 2026-08-24 Phase D). A thin hairline strip locked at the `--mode-h` 28px component height. It DRAWS only while it has something to carry — a message with its one actionLabel (the Undo pattern: a reversible destructive action offers undo here instead of a confirm dialog), or, in the footer's ONE sanctioned case, live work-state segments (the form's save-bar line). The standing records segments, the posture word, and the band's scope segment are DELETED — the counts restated what the surface shows, and the scope leads the bar's subtitle (`AppScaffold.scope`). **THE SILENT REGION** (`Modeline.d.ts` SILENT REGION, THE COLLAPSE IS KEYED PER KIT; #483). Silent, the strip draws nothing and reserves no height, but it is NOT removed: the same element — and the same `liveRegion` semantics node — stays mounted, so a message arriving is a text change inside a region assistive tech already knows, never a region mounting WITH its first message (announced unreliably). A zero-size box will not do on this kit: a semantics node whose rect is empty is dropped from the tree. So the strip keeps its full 28px LAYOUT and is laid OVER the surface's edge — above a footer's bottom edge, below a band's top edge — unclipped, painting nothing and taking no hits, while its reserved height is zero. A host therefore keeps an AppModeline mounted for as long as its surface can message; mounting it on demand is the same defect by another route. The strip ENTERS AND LEAVES on the sheet band (AppMotion.move; a jump-cut under reduced motion): its reserved height grows or shrinks while it fades, and the leaving strip keeps the message it was carrying until it has gone. No clip is ever involved — a clip would empty the region's rect for the flight and drop the node this widget exists to keep. AppModeline owns no `Timer` and no auto-dismiss clock. The host decides when a message appears and clears it (e.g. a delayed callback that sets message back to null after ~6 seconds). The live region is the MESSAGE ALONE — never the action, because an interactive element inside a live region is re-announced by everything around it — and never the ambient segments. **An overflowing message ellipsizes; the action keeps its room** (`Modeline.d.ts`, #760): the message is the one shrinking item, the action never yields, and in the segmented footer the message yields before the segments. **A message may be dismissed** (`Modeline.d.ts`, 2026-10-11, #1342): with onMessageDismiss wired and a message carried, the strip's LAST item is a quiet "Dismiss" ✕ after the action, and Escape with focus inside the strip calls it too. **Two postures** (AppModelinePosture): the footer at the surface's bottom edge, or the band the shell composes under the app bar. One channel per view, never both. ```dart AppModeline( message: '3 records deleted', actionLabel: 'Undo', onAction: undoDelete, onMessageDismiss: clearMessage, ) AppModeline(segments: ['2 required', '1 invalid']) // form save bar ```
AppModelinedefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| actionLabel | String? | null | Label for the single trailing action, beside message (the Undo pattern). Renders only when onAction and message are also non-null — the message-less standalone action (the retired posture-action pattern, Modeline.d.ts 2026-08-24) does not render: a transient band has nowhere to persist a control. |
| message | String? | null | The transient message, e.g. `'3 records deleted'`. Non-null renders it at the strip's trailing edge in DesignTokens.dataS/DesignTokens.ink. The host owns timing: set this when the transient event occurs, clear it after its own duration. |
| onAction | VoidCallback? | null | Callback for the single trailing action. Renders only when actionLabel is also non-null. |
| onMessageDismiss | VoidCallback? | null | A MESSAGE MAY BE DISMISSED (`Modeline.d.ts`, 2026-10-11, #1342). Wired while a message is carried, the strip's LAST item is a quiet dismiss control after the action — KitGlyph.x at 12px in DesignTokens.ink60, named and tooltipped "Dismiss", with the quiet hover (an DesignTokens.ink4 wash, the glyph… |
| posture | AppModelinePosture footer · band | AppModelinePosture.footer | Where the strip sits — AppModelinePosture.footer (default) or AppModelinePosture.band. |
| segments | List<String> | const [] | Live work-state items — the FOOTER's one sanctioned segments consumer (the form's save-bar line, e.g. `['2 required', '1 invalid']`). Rendered in DesignTokens.dataS/DesignTokens.ink60, separated by a `·` divider in DesignTokens.ink40, hugging the strip's leading edge. A records surface passes none… |
AppModelineAction widget
The strip's single trailing action — a compact data-font accent affordance (`.fo-mode__act`), NOT an `AppButton`: a 44px control would inflate the geometry-locked 28px strip. PUBLIC since 2026-08-24: this grammar is also the app bar's TRANSIENT affordance (the dashboard's relocated arrange-gesture…
Full dartdoc
The strip's single trailing action — a compact data-font accent affordance (`.fo-mode__act`), NOT an `AppButton`: a 44px control would inflate the geometry-locked 28px strip. PUBLIC since 2026-08-24: this grammar is also the app bar's TRANSIENT affordance (the dashboard's relocated arrange-gesture Undo — `Dashboard.d.ts` "a transient data-font accent 'Undo' affordance"), so the records chrome renders the same atom rather than a second implementation or a full `AppButton` in the wrong type role. A bar host passes minHeight (`DesignTokens.ctl`) so the hit region meets the touch floor the 28px strip cannot host.
AppModelineActiondefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| label * | String | — | The action's text ("Undo") — data-font accent at emphasis weight. |
| minHeight | double? | null | Minimum hit-region height. Null keeps the strip's own 28px geometry; a bar host passes `DesignTokens.ctl` to meet the touch floor. |
| onAction * | VoidCallback | — | Invoked on tap. |
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.
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 | — |
SyncOfflineLine support
THE OFFLINE LINE's value (`SyncStatus.d.ts`, steady-core #1920): the app on screen and its readiness, which the sync panel states under its status sentence. The host builds it for an app it keeps ready offline, named by its display name, and passes none anywhere else.
| Field | Type | Default | What it does |
|---|---|---|---|
| readiness * | OfflineReadiness | — | How ready the app is. |
| subject * | String | — | The app's display name, verbatim. A blank one draws no line. |
Where it lives
| Class | File |
|---|---|
| AppBanner | packages/app_widgets/lib/src/kit/feedback/banner.dart |
| AppStatusPill | packages/app_widgets/lib/src/kit/feedback/status_pill.dart |
| AppStatusMark | packages/app_widgets/lib/src/kit/feedback/status_mark.dart |
| AppProgress | packages/app_widgets/lib/src/kit/feedback/progress.dart |
| SyncStatusButton | packages/app_widgets/lib/src/sync_status/sync_status_button.dart |
| AppEmptyState | packages/app_widgets/lib/src/kit/feedback/empty_state.dart |
| AppRouteFace | packages/app_widgets/lib/src/floorplans/route_face.dart |
| AppSkeleton | packages/app_widgets/lib/src/kit/feedback/skeleton.dart |
| AppSkeletonLine | packages/app_widgets/lib/src/kit/feedback/skeleton.dart |
| AppBulkBar | packages/app_widgets/lib/src/kit/data/bulk_bar.dart |
| AppModeline | packages/app_widgets/lib/src/kit/data/modeline.dart |
| AppModelineAction | packages/app_widgets/lib/src/kit/data/modeline.dart |