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.

Screens

Records list

Reach for this as the compact-class counterpart of the table — the same collection below the table breakpoint, one primary line per record under the same narrowing chrome. A verb on this screen has three homes and none of them is the bar: the primary one is the Fab (fab), view verbs like Export are the view-options surface's header affordance, and a record's own actions belong to the pushed record surface — see Detail's hosting knob.

Spec of record: design-system/components/data/List.d.ts

13
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.

screen_records_list
Knobs
Theme

Set to 0 for the empty state.

Assembly

Which nav the compact posture mounts — the drawer behind the hamburger, or a bottom bar.

The primary tier, and on a records destination the ONLY loud action the bar's neighborhood has. The scaffold renders it in the thumb zone at every window class; a screen never places it itself.

Actions in the drawer's own footer. The drawer carries them at EVERY window class — at 960 and wider it exists only because they do, since the expanded shell otherwise mounts a standing inline rail instead.

Shows when you open the drawer — the hamburger below 960, an edge swipe at 960 and wider, where the rail head's menu button toggles the rail instead. The actions sit in the drawer's own footer; the resting frame is identical either way. — press Focus to make the preview interactive.

The app's identity mark, and nothing else — it never opens a picker. At 960 and wider it sits beside the menu button in the rail's head at the same place in both postures, overhanging the collapsed rail so the bar beside it steps in. Below 960 the drawer's header band states it.

The scope — one value of the app's own kind (here an Area) — as the FIRST item of the bar's subtitle, ahead of the meta. With a picker it is a quiet text button with a chevron; on a phone the whole title block opens the picker as a bottom sheet. A pushed record's bar shows it read-only.

Global commands with no page ("Send feedback", "Help") pinned in the rail's footer under the footer destinations — the same row as a destination, but never lit and never counted in the destination index.

An organization's or partner's logo at the very bottom of the rail and the drawer — identity, never a control. Collapsed it shows its compact square, or nothing when the host gives none.

View

The Group and Columns panels beside the built-in Filter. `RecordsChrome.panels` is host-contributed and view-agnostic — the same cluster a table gets. OFF leaves Filter standing alone, which is a real posture: the tab row is absent at a single available control, because one control needs no tabs.

Shows when you open the view-options pane (tap Filter's control) — panels are TABS of that one pane, so the resting bar looks identical either way and only the pane's content differs — press Focus to make the preview interactive.

Dart for these values
screen_records_list()

What you can do in it

Capability the preview already carries that no knob can express — you reach it by using the widget. Worth knowing before concluding something is missing: most of this is only clickable once the preview is at Focus size.

The records chrome
  • **Search** — type in the field; the table narrows live. Collapsing search never clears the query, which survives as a removable search:… chip.
  • **Filter** — the funnel opens the view-options pane. Facets are type-appropriate: enums and text offer distinct values, numbers a min/max, dates a From/To.
  • **Chips** — every applied filter renders as a removable chip, and Clear all removes the lot.
  • **Narrowed to zero** states the narrowing and offers Clear, rather than the author-a-record empty state.

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.

AppList widget

The design-system List — a vertical collection of AppListItems, the compact-class posture for record collections (the swap counterpart of a `DataTable` below the table breakpoint). Supply rows via itemCount + itemBuilder (lazy, `ListView.builder` underneath) for large/dynamic collections, or…

Full dartdoc

The design-system List — a vertical collection of AppListItems, the compact-class posture for record collections (the swap counterpart of a `DataTable` below the table breakpoint). Supply rows via itemCount + itemBuilder (lazy, `ListView.builder` underneath) for large/dynamic collections, or children for a small fixed set of already-built AppListItems — exactly one of the two is required. Rows separate with a DesignTokens.hair hairline; the whole list scrolls. Selection mode: AppList does not itself own a selection set — the caller drives AppListItem.checkbox / AppListItem.selected per row (typically a leading AppCheckbox wired to toggle membership in the caller's own selection state, passed through itemBuilder or the children list). Kept deliberately simple, per the spec: tri-state select-all is a table-scope concern, not a list-scope one. Empty state: when the list has zero items (`itemCount == 0`, or children is empty), renders an AppEmptyState built from emptyLabel / emptyActionLabel / onEmptyAction instead of an empty scroll view. Adaptation strategy: **scale** (density only, via DesignTokens.rowV) — the same anatomy at every M3 window class; AppList is itself one half of the table/list **swap** a host performs at the `DataTable` breakpoint. ```dart AppList( itemCount: rows.length, itemBuilder: (context, i) => AppListItem( leading: AppIdChip(id: rowsi.id), title: rowsi.title, subtitle: rowsi.site, meta: rowsi.updatedLabel, selected: rowsi.id == activeId, danger: rowsi.status == 'Blocked', trailing: const KitIcon(KitGlyph.open), onTap: () => openRecord(rowsi.id), ), emptyLabel: 'No records match.', emptyActionLabel: 'Clear filters', onEmptyAction: clearFilters, ) ```

AppListdefault constructor

Knob Type Default What it does
children * List<Widget>? — The fixed row set for the plain AppList constructor. Null for the AppList.builder form.
emptyActionLabel String? null Empty-state single action label. Renders only when onEmptyAction is also non-null.
emptyActionNavigates bool false THE ACTION THAT ONLY OPENS A PAGE (`ActionStrip.d.ts`, #1749): the empty face's action pushes another screen and writes nothing, so it reports a link to assistive technology. Declared, never inferred from onEmptyAction. Default false.
emptyLabel String 'No records.' Empty-state title — what the view would show. Rendered via AppEmptyState when the list has zero rows.
framed bool false The grouped-settings frame (`List.d.ts` `framed`): a strong hairline on the top + inline edges and a soft hairline below, around the whole list — the settings-group posture for a framed cluster of rows on the surface wash. Off by default.
onEmptyAction VoidCallback? null Empty-state single action callback. Renders only when emptyActionLabel is also non-null.
shrinkWrap bool false When true, the list sizes itself to its rows and defers scrolling to an enclosing scroll view (NeverScrollableScrollPhysics) — the posture for a sectioned surface hosting several lists inside one outer scroll. Defaults to false: the list is its own scrollable, filling its viewport.

AppList.builder

Knob Type Default What it does
emptyActionLabel String? null Empty-state single action label. Renders only when onEmptyAction is also non-null.
emptyActionNavigates bool false THE ACTION THAT ONLY OPENS A PAGE (`ActionStrip.d.ts`, #1749): the empty face's action pushes another screen and writes nothing, so it reports a link to assistive technology. Declared, never inferred from onEmptyAction. Default false.
emptyLabel String 'No records.' Empty-state title — what the view would show. Rendered via AppEmptyState when the list has zero rows.
framed bool false The grouped-settings frame (`List.d.ts` `framed`): a strong hairline on the top + inline edges and a soft hairline below, around the whole list — the settings-group posture for a framed cluster of rows on the surface wash. Off by default.
itemBuilder * Widget Function(BuildContext context, int index)? — Builds row `index` for the AppList.builder form — typically an AppListItem, though any widget is accepted (a caller composing a section header inline, say). Null for the fixed-children form.
itemCount * int? — Row count for the AppList.builder form. Null for the fixed-children form.
onEmptyAction VoidCallback? null Empty-state single action callback. Renders only when emptyActionLabel is also non-null.
shrinkWrap bool false When true, the list sizes itself to its rows and defers scrolling to an enclosing scroll view (NeverScrollableScrollPhysics) — the posture for a sectioned surface hosting several lists inside one outer scroll. Defaults to false: the list is its own scrollable, filling its viewport.

AppListItem widget

One record row in the design-system List — the compact-class collection posture (phones, and any surface narrower than the `DataTable` swap threshold). Anatomy, leading to trailing: an optional selection-mode AppCheckbox (wired by AppList, never constructed directly by a caller composing a bare…

Full dartdoc

One record row in the design-system List — the compact-class collection posture (phones, and any surface narrower than the `DataTable` swap threshold). Anatomy, leading to trailing: an optional selection-mode AppCheckbox (wired by AppList, never constructed directly by a caller composing a bare AppListItem), an optional leading identity slot (an AppIdChip, a framed thumb, an avatar — any widget), a title (clamped to two lines, DesignTokens.bodyEm) with an optional subtitle clamped to subtitleMaxLines (default one line; DesignTokens.body/DesignTokens.ink60 — machine-adjacent supporting text, not DesignTokens.dataS; reserve the data font for meta), an optional right-aligned side column stacking meta (DesignTokens.dataS/DesignTokens.ink60 — timestamps, counts) over an optional status word (the spec's first-class status slot, rendered through the shared cell grammar's neutral enum cast; statusDanger takes danger color + em weight), and an optional trailing widget (a chevron, or a single always-visible action / `⋯` menu — **never hover-revealed**, per the constitution's row-actions rule). Selection grammar mirrors the table row exactly: selected paints a 2px DesignTokens.edge accent start-edge over a DesignTokens.rowSel tint wash; danger paints the same edge in DesignTokens.danger with no tint (blocked/error, per the spec's `rowDanger`). The two are mutually exclusive — a selected row's selected wins if both are somehow set, since an open/selected record is the more specific fact in the moment. Full state contract: enabled · hover (DesignTokens.rowHover wash, pointer enhancement only, suppressed while selected) · focus-visible (1px solid accent, zero offset) · pressed (1px translateY on the content, only when onTap is set) · disabled (no onTap → not tappable, no hover) · selected / danger (edge + tint, above). Row height is at least DesignTokens.touch (44 logical px). No ripple. ```dart AppListItem( leading: const AppIdChip(id: 'WO-2041'), title: 'Replace pump seal', subtitle: 'Site 4 — North wing', meta: 'upd 09:41', trailing: const KitIcon(KitGlyph.open), onTap: () => openRecord('WO-2041'), ) AppListItem( title: 'Blocked work order', danger: true, onTap: () => openRecord('WO-2099'), ) ```

AppListItemdefault constructor

Knob Type Default What it does
action Widget? null The row's ONE primary verb (`List.d.ts` `rowAction`/`action`, 2026-08-17) — an end-slot widget rendered BESIDE trailing, before it. For state-driven rows whose next action must be one tap (Download a ready file, Retry a failed run, Cancel a queued one) — burying the verb in the ⋯ menu was rejected…
checkbox Widget? null Selection-mode checkbox, supplied and wired by AppList — a bare AppListItem composed directly by a caller may also pass one, but the common case is AppList wiring this from its own `selectable` state. Renders in a fixed leading slot before leading.
danger bool false Blocked/error grammar: 2px DesignTokens.edge danger start-edge, no tint wash. Ignored when selected is also true.
leading Widget? null Optional leading identity slot — an AppIdChip, a framed thumbnail, an avatar. Any widget; AppListItem does not constrain its shape beyond vertical centering.
meta String? null Optional trailing machine text (DesignTokens.dataS/ DesignTokens.ink60) — a timestamp, a count, a status word. Right- aligned above trailing when both are present.
metaFormat FormatRule<Object?>? null The resolved rule the meta line wears, every paint, the data font kept. Null: the plain line.
note Widget? null A SECOND quiet line in the start column, under subtitle — a WIDGET, not a string, for the one shape a string cannot carry: a live FACT plus the inline affordance that acts on it. `List.d.ts` types the supporting slot as a NODE; this kit narrowed it to a `String`, which is right for the ordinary…
onTap VoidCallback? null Row-tap callback — opens the record. Null renders a non-interactive row (no hover/focus/press paint, no Semantics tap action) — the shape AppList uses while in selection mode is still this same callback wired to toggle selection, so "non-interactive" is genuinely rare (a row with truly nothing to…
selected bool false Selection/open-record grammar: 2px DesignTokens.edge accent start-edge over DesignTokens.rowSel tint. Same encoding a selected table row carries.
semanticLabel String? null Overrides the row's accessible name. Defaults to title (the subtitle and meta are not concatenated in — a screen reader announces the record's name, not its full row text), then titleFormat's label when it has one ("Tank 4, Below minimum").
status String? null Optional status word — the spec's first-class `status` slot, stacked below meta in the right-aligned side column. A status is a WORD (encode once): rendered through the shared cell grammar (`appCellContent`, AppCellType.enumValue) as neutral DesignTokens.body/DesignTokens.ink60 — never a pill, never…
statusDanger bool false Paints status in the danger role — DesignTokens.danger color AND em weight, the spec's `statusDanger` (color only where action is demanded). Ignored when status is absent.
subtitle String? null Optional supporting text (DesignTokens.body/DesignTokens.ink60), e.g. a site name or parent record. Clamped to subtitleMaxLines (default one line), then ellipsized.
subtitleFormat FormatRule<Object?>? null The resolved rule the supporting line (subtitle, `List.d.ts`'s `supporting`) wears, every paint ('tag' plates it on one line). Null: the plain line.
subtitleMaxLines int 1 Line clamp for subtitle. Defaults to 1 (the canonical single-line supporting text); raise it when the supporting text must survive compact widths without truncating (e.g. a two-line description row).
title * String — The record's primary label. Clamped to two lines (DesignTokens.bodyEm).
titleFormat FormatRule<Object?>? null FORMAT RULES ON A ROW'S LINES (`List.d.ts`, #1006): the RESOLVED rule the title line wears — the resolver's output (resolveFormat), never a list. On the title 'tag' reads as 'ink' (a plated name is a badge) and `em` adds nothing. Null: the plain line, as before.
trailing Widget? null Optional trailing widget — a chevron (`onTap`-only rows that merely navigate) or a single always-visible action / `⋯` menu. Never rendered conditionally on hover: the constitution bans hover-revealed row actions outright, and touch surfaces have no hover to reveal on.

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.

FormatRule support

One FORMAT RULE (`FormatRules.d.ts` THE FORMAT RULE): a match and its effects. A rule list is ORDERED and the FIRST holding rule formats the value; a rule with no match always holds, so written last it is the column's default.

Field Type Default What it does
color KitColor? null A palette name or a color parsed from data — on a map pin, its FILL.
em bool false The word at the em weight.
glyph KitIconRef? null A glyph — on a map pin, the head's glyph.
italic bool false The word italic.
label String? null What the rule means, in the host's words — read by a legend, a chip and the accessible name.
match FormatMatch<T>? null The predicate, or null for "always holds" (the default rule).
muted bool false The word at ink40 (ignored under FormatPaint.ink and FormatPaint.tag).
paint FormatPaint FormatPaint.mark How color lands on a value (FormatPaint.mark by default).
strike bool false The word struck through, in its own ink.

Where it lives

Class File
AppList packages/app_widgets/lib/src/kit/data/list.dart
AppListItem packages/app_widgets/lib/src/kit/data/list.dart