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

Charts

The plots, the units that frame them, the KPI marks, and the three states a tile reaches when there is nothing to draw. Drawn over real-shaped data, because a chart over a clean curve flatters itself.

Spec of record: design-system/components/charts/Charts.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_charts
Knobs
Theme
Dart for these values
family_charts()

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.

ChartUnit widget

The fused KPI-header chart unit (north-star #1) — one anatomy for every chart card: muted label → display-size tabular value → delta figure → bare plot → endpoint labels. The number is the product; the plot is context (no y-axis, no gridline apparatus on platters).

Full dartdoc

The fused KPI-header chart unit (north-star #1) — one anatomy for every chart card: muted label → display-size tabular value → delta figure → bare plot → endpoint labels. The number is the product; the plot is context (no y-axis, no gridline apparatus on platters). Display-only, read-only (T1: zero writes). The unit renders flat (operations dashboards, D2); composition tiles add card chrome at the dashboard layer. Freshness is hoisted to the dashboard's single screen-level line — rollupCaption renders only when a day-dim chart's backend rollup as-of genuinely diverges (the caller decides). **A unit that is a dashboard tile's WHOLE BODY fills it** (`Charts.d.ts`, the S1a-1 clause): the unit takes the body's height, its anatomy keeps its own, and its platter slot takes the height LEFT — an `Expanded` in place of the fixed `SizedBox(plotHeight)` — so the platter fills the slot as it would the body. The tile body declares itself through the kit-internal framed-host scope (`DashboardTile` installs it around its body, and only there), and the unit fills only where that body hands it a BOUNDED height: a body that scrolls its content (`fillsBody: false`) gives an unbounded one, and there the fixed slot holds, exactly as the web's `block-size: 100%` resolves to its content height against an indefinite parent. The fixed slot heights hold everywhere else. **THE PLOT KEEPS HALF** (`Charts.d.ts`, #864, #960). Filling, the unit gives its platter slot at least half its own height, and the legend yields to keep it: its line budget drops from two to ONE, then to its more chip alone, which rides the label line's inline end rather than a line of its own. The label and the headline never yield, and the column never overflows the body — the budget is handed to the legend through ChartLegendBudgetScope. The fixed slot is unaffected: there the legend's own two-line budget is the whole bound. A legend in the SCROLL posture (`ChartLegendOverflow.scroll`, THE LEGEND SCROLLS PAST ITS CAP, #1065) yields from two lines to one and never further: it keeps its own slot and scrolls inside that one line. **A FILLING UNIT PADS ITSELF BY THE CONTENT INSET** (`Charts.d.ts`, 2026-10-01, #976). The whole-body unit sits in a FLUSH body (its platter owns its edge), so the inset is the unit's own: THE CONTENT INSET, kDashboardTileContentInset (12), on all four sides of its whole column at every density — the label line, the headline, the legend, the slot, the rule, the endpoint labels and the caption — where it padded by the row density (`rowV` × 2 inline, `rowV` block). A chart tile's label then sits where a flow tile's first heading does. Outside a filling tile body the row-density padding is unchanged. **THE HEADLINE SAYS WHAT IT IS, AND MAY STAND DOWN** (`Charts.d.ts`, 2026-10-01, #1028). headline ChartHeadline.none draws no head row, and the slot takes its height. A shown headline carries headlineCaption — the reduction and the window, "Sum · last 30 days" — on its own line 4 under the value, and the spoken summary says it after the value.

ChartUnitdefault constructor

Knob Type Default What it does
delta ChartDelta? null The resolved delta (THE DELTA IS A FIGURE, ChartDeltaFigure); null renders none.
deltaCaption String? null The comparison-window caption ("vs prior 30d"). Absent in the no-baseline state by construction.
density AppDensity
comfortable · compact · condensed
AppDensity.comfortable Density tier (shared F8 enum). Charts implement comfortable and compact; `condensed` is table-only and renders as compact here.
empty ChartEmptyState? null The empty-frame content (Δ12). When set, the headline shows the em-dash placeholder and the plot region hosts the ghost + CTA.
endLabel String? null End endpoint x label (footer).
headline ChartHeadline
auto · none · always
ChartHeadline.auto Whether the KPI headline renders (#1028). ChartHeadline.auto, the default, draws it on a unit, as ChartHeadline.always does; ChartHeadline.none draws no head row at all — no value, no — mark, no delta, no headlineCaption — and the spoken summary is the label alone.
headlineCaption String? null THE CAPTION under a shown headline — the reduction and the window, joined " · " ("Sum · last 30 days"). The label role at weight 400 in `ink40`, one line ellipsized, 4 under the value line. It is part of the headline, so it never yields to THE PLOT KEEPS HALF.
hideDataButton bool false Hides the *visible* overflow button (origin `HideDataButton: true` on 103/104) — the semantics custom action still remains (Δ4).
label * String — Unit label (header row, muted).
legend Widget? null Optional legend row — normally a ChartLegend. A single-series chart never gets one.
noBaselineLabel String kDeltaNoBaselineLabel The no-baseline caption (THE DELTA IS A FIGURE — "No prior period", the kits' shared default copy).
onChartData VoidCallback? null The Δ4 "Chart Data" escape hatch: routes to the slice-bound `DataTableView`. When set, an overflow action is offered and the "View data table" semantics custom action is **always** exposed (the WCAG 2.5.8 equivalent control + the SC 1.1.1 text alternative for dense marks — non-removable). Null…
plot * Widget — The plot region content. Excluded from semantics (Δ9) — except a kit platter that states its own: a temporal plot's keyboard stop (THE PLOT TAKES THE KEYBOARD, #1427) and a categorical platter's door rows.
plotOwnsBaseline bool false Whether the PLATTER closed its own plot on the zero line, so this unit drops its baseline rule. `Charts.d.ts` → Axes states the ownership once: *"The zero baseline is never a gridline — under ADAPTIVE the platter closes its own plot on it and the unit DROPS its rule (one zero line, drawn by whoever…
rollupCaption String? null Day-dim rollup divergence caption — only when the backend rollup as-of differs from the dashboard freshness line.
showLabel bool true Whether the header row renders the visible label text. False is for a host whose tile chrome ALREADY titles this unit (an `AppChartTile` header naming the same thing) — the label then serves semantics only, so the tile never states its name twice (chrome review 2026-08-16, C9). label stays required…
startLabel String? null Start endpoint x label (footer).
table bool false THE TABLE IS THE KIT'S (`Charts.d.ts`, 2026-10-14, #1427, D12). Default false. True puts ONE quiet ghost icon button at the inline end of the label line — the `table` glyph named "Show table" while the chart shows, the `chart` glyph named "Show chart" while the table shows; its name says what a…
value * String? — The pre-formatted headline value (the format engine owns T8/T16). Null renders the em-dash placeholder — the frame never collapses or reflows when data arrives.

ScrubbableChartUnit widget

A ChartUnit whose plot is scrub-to-read (Δ2, line + count-bar ONLY — the donut and categorical bars use tap/legend, never a dragged indicator). Long-press-then-drag (a gesture proxy so the dashboard's vertical scroll survives) plants a finger-tracked vertical indicator; the **headline swaps…

Full dartdoc

A ChartUnit whose plot is scrub-to-read (Δ2, line + count-bar ONLY — the donut and categorical bars use tap/legend, never a dragged indicator). Long-press-then-drag (a gesture proxy so the dashboard's vertical scroll survives) plants a finger-tracked vertical indicator; the **headline swaps instantly** to the datum under the finger (text never lerps) and the unscrubbed plot dims to `chartDimAlpha`; release restores the resting headline. A datum-boundary crossing fires HapticFeedback.selectionClick. Read-only (T1). The swap is presentation of the same local bucket the painter drew — the drill-inert flag suppresses only the Δ3 door, never the read.

ScrubbableChartUnitdefault constructor

Knob Type Default What it does
delta ChartDelta? null The resolved delta — hidden while scrubbing.
deltaCaption String? null The comparison-window caption ("vs prior 30d") — hidden while scrubbing.
density AppDensity
comfortable · compact · condensed
AppDensity.comfortable Density tier (forwarded to ChartUnit).
endLabel String? null End endpoint x label (footer, forwarded to ChartUnit).
headline ChartHeadline
auto · none · always
ChartHeadline.auto Whether the headline renders (forwarded to ChartUnit.headline, `Charts.d.ts` THE HEADLINE SAYS WHAT IT IS, AND MAY STAND DOWN, #1028). Under ChartHeadline.none there is no head row for a scrub to swap, and the plot still scrubs.
headlineCaption String? null The resting headline's caption — the reduction and the window (forwarded to ChartUnit.headlineCaption). Hidden while scrubbing, as delta is: the swapped value is one bucket, which the window's reduction does not describe.
label * String — Unit label (forwarded to ChartUnit).
onScrubChanged ValueChanged<int?>? null Fires the resolved index while scrubbing and `null` on release — the host swaps the Δ3 drill chip / callout off this (drill-inert days pass their index but the host suppresses the door).
plot * Widget — The platter (count-bar or trend line). Wrapped with the indicator + dim overlay.
plotOwnsBaseline bool false Whether the wrapped platter closed its own plot on the zero line (forwarded to ChartUnit.plotOwnsBaseline — the contract and its reasoning live there). A scrubbable unit hosts an adaptive platter by default, so this is the knob that stops the zero line being drawn twice.
restingValue * String — The resting (latest-datum) headline value — shown when not scrubbing.
rollupCaption String? null Day-dim rollup divergence caption (forwarded to ChartUnit).
scrubData * List<ScrubDatum> — The scrub-resolvable series, index-aligned to the plotted marks.
showLabel bool true Whether the visible label row renders (forwarded to ChartUnit.showLabel) — false when the host's tile chrome already titles this unit; label still voices the semantics summary.
startLabel String? null Start endpoint x label (footer, forwarded to ChartUnit).

AppChartTile widget

The dashboard tile chassis — THE HOUSE CARD (`Charts.d.ts` ChartTile: DesignTokens.paper, the DesignTokens.cardEdge hairline, DesignTokens.rM corners, DesignTokens.sh1) with a title-role header over a DesignTokens.hair rule. THE CARD HEAD WINS OVER A PADDED CARD (`Charts.d.ts` `ChartTileProps`,…

Full dartdoc

The dashboard tile chassis — THE HOUSE CARD (`Charts.d.ts` ChartTile: DesignTokens.paper, the DesignTokens.cardEdge hairline, DesignTokens.rM corners, DesignTokens.sh1) with a title-role header over a DesignTokens.hair rule. THE CARD HEAD WINS OVER A PADDED CARD (`Charts.d.ts` `ChartTileProps`, #1428): the card itself is unpadded. The HEAD is at least DesignTokens.cardHeadH (48) tall, padded 16 inline-start and 12 inline-end, the title one ellipsized line, a DesignTokens.hair rule beneath it; the BODY is padded 12 block and 16 inline, so the title and the content share one start edge. The error surface sits in the body and takes no padding of its own. The tile carries NO outer margin: the host spaces its tiles. ```dart AppChartTile(title: 'Observations / day', child: ChartUnit(...)) AppChartTile( title: 'Action items', errorLabel: 'Action items', onRetry: retry, error: true, ) ```

AppChartTiledefault constructor

Knob Type Default What it does
child Widget? null The tile body (typically a `ChartUnit` or platter composition).
error bool false Renders the error surface in place of child.
errorLabel String? null The error surface's subject label; defaults to title.
onRetry VoidCallback? null The error surface's Retry action.
title * String — The header text — title role.

ChartLegend widget

The unit's legend row: one chip per series, on at most maxLines lines. Anatomy is `Charts.d.ts` THE LEGEND CHIP (the owner's design pass, `.lg`) — chips are real buttons at the touch floor carrying the full state contract, each a 10 swatch at `rMark`, an optional glyph, the word in the UI font at 12…

Full dartdoc

The unit's legend row: one chip per series, on at most maxLines lines. Anatomy is `Charts.d.ts` THE LEGEND CHIP (the owner's design pass, `.lg`) — chips are real buttons at the touch floor carrying the full state contract, each a 10 swatch at `rMark`, an optional glyph, the word in the UI font at 12 in ink60, and an optional quiet count; a PRIOR chip is a key, not a button. In a dashboard tile's CARD HEAD the tile caps it at one line (THE LEGEND RIDES THE CARD HEAD) through ChartLegendBudgetScope. **THE LEGEND IS BOUNDED** (`Charts.d.ts`, #864). Chips lay out in paint order at the legend's width, a word ellipsizing within it rather than wrapping. When a chip would open a line past the budget, it and every chip after it give way to ONE trailing MORE chip ("+N", the data role, spoken "N more series") that opens the whole list — every chip, in paint order, in the same mode — on the kit's options surface: an anchored popover at the chip, a bottom sheet at compact touch. While a series in the overflow is raised (highlight), hidden (visibility) or applied (filter), the more chip wears its pressed face, so a state the reader set never disappears. Under ChartLegendOverflow.scroll there is no more chip: the chips wrap and scroll inside the cap instead (overflow). **THE LIT SERIES** (#1065): the chip whose label rides the enclosing ChartLitScope takes the raised face — the word at the em weight in ink — in every mode, without changing what its press means.

ChartLegenddefault constructor

Knob Type Default What it does
hidden Set<String> const {} The labels currently hidden — visibility mode only. Ignored without onToggle, because there is nothing for it to mean.
inert Set<String> const {} Labels whose chips are KEYS, not buttons, in filter mode — as a prior chip is: no press, no pressed face, no focus stop. A split's "—" chip, which names no value a row holds.
items * List<ChartLegendItem> — One entry per series, in paint order.
maxLines int kChartLegendMaxLines THE LEGEND IS BOUNDED: the lines the chips may take before the rest fold into the "+N" more chip. Default kChartLegendMaxLines; 0 renders the more chip alone ("N series"). A framing unit may lower it through ChartLegendBudgetScope (THE PLOT KEEPS HALF), never raise it.
onSelect ValueChanged<String>? null Present → FILTER mode (`Charts.d.ts` ChartLegendProps, 2026-10-02, #1081): a chip tap REPORTS its label and the caller writes a filter with it; the caller owns selected. Pressed reads "this value is applied", so no chip is pressed at rest. It neither raises nor hides — the data a filter narrows is…
onToggle ValueChanged<String>? null Present → VISIBILITY mode: reports the toggled chip's label, and the caller owns hidden. Absent → HIGHLIGHT mode.
overflow ChartLegendOverflow
more · scroll
ChartLegendOverflow.more THE LEGEND SCROLLS PAST ITS CAP (`Charts.d.ts`, #1065): what the chips do past maxLines. Default ChartLegendOverflow.more, THE LEGEND IS BOUNDED as written; ChartLegendOverflow.scroll wraps every chip and scrolls inside the cap.
selected Set<String> const {} The labels currently applied — filter mode only.

ChartRangePresets widget

The range-preset row (`Charts.d.ts` `ChartRangePresets`) — the four blessed presets as the APPLIED-TOGGLE grammar: outline AppButtons (AppButtonVariant.outline) worded in the data face at the small data size (DesignTokens.dataS's face and size), the row gapped by DesignTokens.rowV; the active preset…

Full dartdoc

The range-preset row (`Charts.d.ts` `ChartRangePresets`) — the four blessed presets as the APPLIED-TOGGLE grammar: outline AppButtons (AppButtonVariant.outline) worded in the data face at the small data size (DesignTokens.dataS's face and size), the row gapped by DesignTokens.rowV; the active preset carries accent border + accent text (AppButtonTone.accent, the constitution's "applied-but-closed" voice). Never a `ChoiceChip` row — Material's chip selection re-encodes the state with a fill + checkmark this system does not speak. Device-local selection; presets are the equivalent path to zoom (zoom is never the only way to narrow the window; A11y). THE ACTIVE PRESET IS STATED (`Charts.d.ts` `ChartRange`, #1428): the presets are pressed-state toggles — the row one `Semantics` container labeled "Range", each preset's one button node carrying `Semantics(toggled:)`, true on the active preset alone (SC 1.4.1).

ChartRangePresetsdefault constructor

Knob Type Default What it does
onChanged * ValueChanged<ChartRange> — Fires with the tapped preset (re-tapping the active one re-fires).
selected * ChartRange
d7 · d30 · d90 · all
— The active preset.

TrendLinePlatter widget

The trend-line platter — the value-over-time surface (`Charts.d.ts` → TrendLineProps). One or more ChartLineSeries over a SHARED time domain, and **THE TIME AXIS IS THE CALENDAR** (`Charts.d.ts`, 2026-10-02, #1079): the x domain is EVERY calendar day from the earliest day any series states to the…

Full dartdoc

The trend-line platter — the value-over-time surface (`Charts.d.ts` → TrendLineProps). One or more ChartLineSeries over a SHARED time domain, and **THE TIME AXIS IS THE CALENDAR** (`Charts.d.ts`, 2026-10-02, #1079): the x domain is EVERY calendar day from the earliest day any series states to the latest, inclusive, and a day sits at its own place in that run — never at its index among the days that happen to carry data, which is how three readings in March once took the width of seven weeks of April and May. A point's day is the calendar day its date STATES (chartDay); a second point on one day replaces the first. What the line does across a DAY WITH NO POINT is the gaps knob (A GAP IS JOINED BY DEFAULT, #1427): ChartGaps.connect by default joins the readings, ChartGaps.bridge draws the stretch dotted, and ChartGaps.breakLine lifts the pen and marks an isolated reading. The area fill, the prior ghost and the subset follow the same knob. Every read stays honest under all three: a day with no point reads the em dash. The web twin is `TrendLine.tsx`. **Color is the tier's, never the caller's.** Series *i* paints AppChartTheme.seriesColorAt of `seriesi.series ?? i`; a caller pins a ramp POSITION, and there is no color parameter to pass. A series that carries a `tone` paints AppChartTheme.toneColor of it instead — its line, area, resting and active points and its tooltip swatch (A SERIES MAY WEAR A STATUS TONE, #1425) — and its words stay ink. **The y story is zero-anchored, always** — `minY` is 0 under every axis mode, because the bottom border IS the zero baseline and every gridline is a step multiple of it. That is `ChartFrame`'s first caller precondition, and it is why the padded-nice band the v1 platter used is gone: a non-zero floor draws a "zero line" that is not at zero. **Motion**, per `Charts.d.ts` → Motion: * ENTRANCE (once per mount, ChartEntranceMixin) — each series' path is traced left-to-right over `--t-draw` from its own per-series stagger offset, its area fading in on the same clock. While that runs the platter renders a LAYERED tree: one base chart carrying the frame and the ghost, and one revealed overlay chart per series above it. * SETTLED — one combined chart, so a data change MORPHS through fl_chart's implicit lerp on the update clock (chartUpdateClock — `--t-move`, `--t-quick` under the dashboard bus) instead of blanking and redrawing. A data change that arrives mid-entrance completes the entrance instantly and hands over to that morph. **Highlight** rides the enclosing unit's ChartHighlightScope: while a label is raised, every OTHER labeled series drops to the ghost treatment (the specimen's `.fo-ser--ghost`, a group opacity, ported here as the AppChartTheme.ghostAlpha multiplier on each series' own paint) and the raised one's trailing mark becomes THE ACTIVE POINT (a 9 paper square stroked 2 in the hue — `Charts.d.ts` THE ACTIVE POINT IS A SQUARE). A series with no label is never raised and never dimmed, so the single-series posture is untouched by a channel it cannot participate in. At rest, **every reading carries its mark while the plot can hold one** (`Charts.d.ts` THE LINE IS STRAIGHT, #1424, D2): while THE PITCH — the run between two neighboring days' points — is at least AppChartTheme.markPitch (10), every point of every series draws the 6 resting square filled in its hue; under it, and always under ChartAxisMode.none (a sparkline shows shape), only each series' latest reading does, plus its isolated ones under ChartGaps.breakLine. A sparkline always connects, whatever gaps says. The prior ghost and the subset draw no marks. MARKS BEAT ANNOTATIONS (#1427): while a reference is drawn, or a bridge, the resting marks paint in a pass of their own ABOVE it. **The platter SCRUBS** (`Charts.d.ts` → Interaction; #958, the line twin of the count bars' #788). The day nearest the pointer — every day of the calendar run — takes a hairline indicator, every series with a point there takes THE ACTIVE POINT, and the shared ChartTooltip names the day and one row per series, value first and each value carrying unit (the em dash for a series with no point that day), closed by the prior-period row when priorAligned has a value that day. A crossing into a new day fires a selection haptic. It is a READ and commits nothing. Under enableZoom the scrub stands down: the zoomed plot's x no longer maps onto the days the indicator would name. Under a ScrubbableChartUnit, whose own scrub drives the headline, it stands down too, so one gesture never raises two readouts. **THE PLATTER'S GESTURES** (`Charts.d.ts`, #1081): a TAP reports the day nearest it through onDayTap; the SCRUB reads; the BRUSH commits a band. Which motion is which follows the pointer — on touch with a brush wired a horizontal drag scrubs and a long-press then drag brushes; on touch with none a long-press then drag scrubs; a mouse hovers to scrub and drags to brush — and none claims a vertical pan, so the board still scrolls through the plot (`ChartPlotGestures` states the grammar once for both temporal platters). **THE LIT SERIES** (#1065): a scrub over more than one series lights the series whose point on the scrubbed day lies nearest the pointer's height (no point that day, no candidate; a tie takes the lower position) on the enclosing ChartLitScope, and clears it when the scrub ends — the legend chip of that series takes the raised face. Past six series the card lists six rows (THE TOOLTIP PAST SIX ROWS): the lit series and the five whose values lie nearest its value, in paint order — the first six in paint order when nothing is lit — then "+N more". **DAILY TICKS** (dailyTicks, #1080): every day labeled, rotated past the pitch, and under kChartDayPitch the plot scrolls inside the platter, its value gutter pinned and its latest day in view. **THE BUCKET FOLLOWS THE BAND** (granularity, #786): the run steps a day, an ISO week or a calendar month at a time, each point keyed by its bucket's first day. A TRAILING PARTIAL bucket (ChartLineSeries.partialDays) leaves the line: the segment that reaches its point and its mark draw at AppChartTheme.partialAlpha, and its card adds "· 3 of 7 days so far". THE LATEST READING IS THE PARTIAL BUCKET (owner ruling 2026-10-14): where only the latest reading is marked, that faint mark is the series' one — the last whole bucket draws none. **THE PLOT MAY CARRY THREE ANNOTATIONS** (`Charts.d.ts`, #1426, D7): a host-stated reference line, THE END LABEL beside the latest reading (endLabel, on by default) and THE PEAK LABEL above the greatest one (peak). The end and peak labels are SINGLE-SERIES only — a split names its lines through the legend and THE LIT SERIES — and none of the three draws under ChartAxisMode.none. The reference and end labels stand in THE END GUTTER at the plot's inline end (`ChartFrame.endGutter`).

TrendLinePlatterdefault constructor

Knob Type Default What it does
area bool? null The gradient area fill. Null resolves to `series.length == 1` — lines always overlap from the baseline, so translucent fills beyond one series muddy every hue. An explicit `true` is honored at any count: the default is a default, not a cap.
axis ChartAxisMode
none · endpoints · adaptive
ChartAxisMode.adaptive The plot frame this platter draws for itself. Never inferred — a sparkline passes ChartAxisMode.none explicitly.
brush (double, double)? null The COMMITTED brush band, normalized 0–1 across the PLOT width (`Dashboard.d.ts` brush) — rendered as the static accent-tint band. It normalizes against the calendar run: its first day on the plot's start edge, its last on its end edge (and, while the plot scrolls under dailyTicks, against the WHOLE…
crosshairDay DateTime? null THE HOVERED DAY IS SHARED (`Dashboard.d.ts`, 2026-10-14, #1428): a day another surface is reading — on a board, the bus's hovered day. The platter draws its CROSSHAIR ALONE on the bucket holding it (a week or a month holds every day in it): no tooltip, no active point and no announcement, while no…
curve ChartCurve
monotone · linear
ChartCurve.linear How the points are joined (`Charts.d.ts` THE LINE IS STRAIGHT, #1424): ChartCurve.linear by default; ChartCurve.monotone is the opt-in for a continuous measurement, drawn on Fritsch–Carlson tangents so the curve never passes a reading. The prior ghost and the subset take the same joins as the series…
dailyTicks bool false DAILY TICKS (`Charts.d.ts`, 2026-10-02, #1080): under ChartAxisMode.adaptive, EVERY day of the run carries its tick label — flat while the widest label plus 12 fits the pitch, else rotated −45° and end-anchored under its day; and where the pitch would fall under kChartDayPitch the plot takes `days ×…
enableZoom bool false Enables pinch-zoom/pan on the expanded tier: a horizontal-axis `FlTransformationConfig`. Off on platters (tile tier), and mutually exclusive with the brush by contract.
endLabel ChartEndLabel
value · none
ChartEndLabel.value THE END LABEL (`Charts.d.ts`, #1426): ChartEndLabel.value (the default) states the latest reading's value with unit ("48 L") in the data role at 600 in ink, centered on the latest point in THE END GUTTER; it stays while a scrub is up. SINGLE SERIES ONLY: more than one series draws none whatever this…
gaps ChartGaps
connect · bridge · breakLine
ChartGaps.connect What the line does across days with no reading (`Charts.d.ts` A GAP IS JOINED BY DEFAULT, #1427): ChartGaps.connect by default joins the readings, ChartGaps.bridge draws the stretch dotted in the series hue at the dim alpha, and ChartGaps.breakLine lifts the pen. The area fill, the prior ghost and…
granularity ChartGranularity
day · week · month
ChartGranularity.day What each point's bucket spans (`Charts.d.ts` THE BUCKET FOLLOWS THE BAND, 2026-10-14, #786): ChartGranularity.day by default. Under a week or a month the run steps a bucket at a time from the first bucket any series states to the last, and a point sits on the bucket holding its day (its first day,…
onBrush ValueChanged<(double, double)?>? null Reports the LIVE band while a brush drag is in flight — the range that would commit right now, so a host can preview it. It is null while the span sits under the 3% floor (the drag would clear rather than commit), and null once more on release, when the live band is gone and onBrushEnd owns what…
onBrushEnd ValueChanged<(double, double)?>? null Enables the BRUSH — a long-press then drag on a coarse pointer, a press-drag on a fine one (THE PLATTER'S GESTURES, #1081) — and commits on release: a span under 3% of the plot width commits null (clears the range) instead of a sliver. Null leaves the platter brush-free.
onDayTap ValueChanged<DateTime>? null THE PLATTER'S GESTURES (`Charts.d.ts`, 2026-10-02, #1081): a tap (a click) reports the CALENDAR DAY nearest it — a date-only UTC value, the day as chartDay states it; under a coarser granularity, the bucket's first day. It is the platter's primary tap: it raises no tooltip and no highlight. Null and…
peak bool false THE PEAK LABEL (`Charts.d.ts`, #1426): true labels the series' GREATEST reading (the later day on a tie) in the small data role in `ink40`, 4 above its resting mark, and grows the head room by AppChartTheme.peakRoom. OMITTED when the peak is the latest reading — the end label already says it. Single…
priorAligned List<(DateTime, double)>? null The prior-period comparison ghost for the FIRST series (`Charts.d.ts` THE PRIOR PERIOD IS A GHOST): a AppChartTheme.priorStroke line in `ink40` at full opacity, dashed AppChartTheme.dashPattern, drawn UNDER the lines. Already re-based onto the current window's dates by `alignPriorPeriodByOffset`…
reference ChartReference? null THE REFERENCE LINE (`Charts.d.ts`, #1426): a target or threshold the host states — a 1 `ink60` line dashed 2,3 across the plot at its value on a 3 `paper` underlay, drawn OVER the gridlines, the lines and the area (THE REFERENCE READS OVER THE MARKS) and UNDER every point mark: the resting and…
series * List<ChartLineSeries> — The series to plot, in paint order. One entry is the single-series posture.
subset List<(DateTime, double)>? null The TRANSIENT highlight subset — the lead-series path (ramp s0) drawn OVER the population line, which STEPS BACK to DesignTokens.ink20 while a subset shows (THE CROSS-FILTER STATE, #895), during a hover/press elsewhere on the plate; commits nothing. Day-matched onto the calendar run like…
tapVerb String? null THE TOOLTIP NAMES THE TAP (`Charts.d.ts`, 2026-10-14, #1427): the verb a tap on the scrubbed day performs ("filter to this day"). While onDayTap is wired the card's LAST line names it — `Click to <verb>` at a fine pointer, `Tap to <verb>` on a coarse one, `Press Enter to <verb>` from the keyboard —…
unit String? null The display-only unit of the values this platter spells (`Charts.d.ts` A VALUE'S UNIT): "12.5 L" on every gridline label, never on an x tick. A label, never a transform — the values are never rescaled. Null or empty reads the bare value, as before.

SmallMultiplesPlatter widget

The small-multiples platter — one PANEL per line series (`Charts.d.ts` SmallMultiples, 2026-10-14, #1428, D13). Eight sparse levels drawn over each other in one plot are a tangle nobody can follow; the same eight side by side, on one scale, are eight readings a reader can compare. * **THE GRID.**…

Full dartdoc

The small-multiples platter — one PANEL per line series (`Charts.d.ts` SmallMultiples, 2026-10-14, #1428, D13). Eight sparse levels drawn over each other in one plot are a tangle nobody can follow; the same eight side by side, on one scale, are eight readings a reader can compare. * **THE GRID.** One panel per series, in paint order, in two columns while each panel gets at least AppChartTheme.panelMinWidth (240) — COLUMNS FOLLOW THE PANEL, NOT THE SLOT (owner ruling 2026-10-14) — and one below, AppChartTheme.panelGap (12) apart on both axes. Each panel is a HEAD over a PLOT, and every row above the last keeps a FOOT of the head room's 6 under its zero line, so its "0" label is never cut. The panels share a bounded slot's height evenly while every plot keeps AppChartTheme.panelPlotMin (72); past that the grid scrolls inside the slot (SmallMultiplesLayout). * **THE HEAD**, AppChartTheme.panelHeadHeight (20) tall: the series' 10 square swatch, its label in the label role in `ink60`, ellipsized, and at the inline end its LATEST reading in the data role at 600 in ink with unit, never cut. The heads are the legend: the platter takes none. * **ONE SCALE.** THE SCALE runs once over every series' values and every panel draws the same gridlines and labels them; the x domain is the window's calendar run, shared, and only the LAST row draws the x ticks. * **THE SERIES LINE** is straight, its resting marks at the panel's own pitch (every reading while it holds AppChartTheme.markPitch, else the latest alone — a panel always bridges, and only `gaps: 'break'` marks an isolated reading), in the series' color, with no area. THE LATEST READING IS THE PARTIAL BUCKET: a trailing partial point is the latest, so it takes the one mark, at AppChartTheme.partialAlpha. * **THE AVERAGE**: a 1 `ink40` line dashed 4,4 across the panel at the mean of the series' readings in the window, under the series line, unlabeled. * **GAPS ARE BRIDGED, VISIBLY NOT DATA**: always ChartGaps.bridge — a stretch across readingless days is a 1.5 line dashed 1.5,3 in the series hue at AppChartTheme.chartDimAlpha. No tooltip row, readout or table cell ever reads it: they read the em dash. No `gaps` knob. * **ONE CROSSHAIR.** A scrub in any panel, or the keyboard (the whole grid is ONE stop, THE PLOT TAKES THE KEYBOARD), chooses one bucket for EVERY panel: each draws the crosshair and its active point there, and the scrubbed panel alone raises the tooltip, for its own series — the first panel's under the keyboard. The live readout speaks every series that day, in paint order. * onDayTap, tapVerb, unit, granularity and crosshairDay mean what they mean on TrendLinePlatter. There is no brush, no prior period, no subset and no annotation. The dashboard chart tile chooses this platter for a sparse split line (`Dashboard.d.ts` THE FORM IS A KNOB).

SmallMultiplesPlatterdefault constructor

Knob Type Default What it does
axis ChartAxisMode
none · endpoints · adaptive
ChartAxisMode.adaptive Default ChartAxisMode.adaptive. ChartAxisMode.none asserts in debug and draws adaptive in release; ChartAxisMode.endpoints draws no x tick row on any panel.
crosshairDay DateTime? null THE HOVERED DAY IS SHARED (#1428): another surface's day. Every panel draws its crosshair alone on the bucket holding it, while no scrub of the platter's own is up. Null, or a day outside the run, draws nothing.
granularity ChartGranularity
day · week · month
ChartGranularity.day What each bucket spans (THE BUCKET FOLLOWS THE BAND, #786).
onDayTap ValueChanged<DateTime>? null A tap in any panel reports the bucket's first day nearest it; Enter on a keyboard-chosen bucket does the same. Null and a tap reports nothing.
series * List<ChartLineSeries> — The series, one panel each, in paint order.
tapVerb String? null THE TOOLTIP NAMES THE TAP (#1427): the verb a tap performs, on the card's last line and at the end of the live readout while onDayTap is wired.
unit String? null The display-only unit (A VALUE'S UNIT): on every gridline label, in each head's latest reading and in the tooltip; never on an x tick.

CountBarPlatter widget

The count-bar platter — the count-over-time surface (`Charts.d.ts` → CountBarsProps). ChartBarSeries over a zero-filled DayDatum axis: one bucket per calendar day, synthetic days rendering flat so the time axis stays honest (Δ5).

Full dartdoc

The count-bar platter — the count-over-time surface (`Charts.d.ts` → CountBarsProps). ChartBarSeries over a zero-filled DayDatum axis: one bucket per calendar day, synthetic days rendering flat so the time axis stays honest (Δ5). Every bucket gets a GROUP even at a value of zero — the specimen skips PAINTING an empty bar, but fl_chart indexes its bottom titles by list position, so dropping the group would slide every tick label onto the wrong day. The FIRST series owns the x domain: every series is zero-filled to the same buckets before it reaches the platter (`ChartBarSeries`), and a series shorter than that domain contributes 0 there, never a hole. **Color is the tier's, never the caller's.** A series paints AppChartTheme.seriesColorAt of its own `series` position, defaulting to its index; a caller pins a ramp POSITION and there is no color parameter. A series that carries a `tone` paints AppChartTheme.toneColor of it instead — its rods and its tooltip swatch, and a host's legend chip takes the same color (A SERIES MAY WEAR A STATUS TONE, #1425) — and its words stay ink. **mode says how several series share a bucket** (`ChartBarMode`; the web kit's `CountBars` `mode`): ChartBarMode.stacked sums them into ONE rod whose segments keep their hues, each segment that rests on another starting AppChartTheme.stackGap (2) above it — a TRUE gap of clear plot, never a stroke (`Charts.d.ts` THE BAR FILLS ITS BAND, #1424, D4); ChartBarMode.grouped sets one rod per series side by side within the bucket. ChartBarMode.single with more than one series is a CALLER ERROR — see the constructor's assert. **The y story is zero-anchored, always.** `minY` is 0 under every axis mode, and while every value is a WHOLE number the scale takes THE SCALE's WHOLE arm (ChartFrame's `whole`: no step under 1, no 2.5), so a three-row window can never resolve half a row. A Sum or an Average with a fraction in it lifts that floor (`Charts.d.ts` THE CHART TILE'S AGGREGATION): there a 0.5 gridline is a value somebody measured. The web kit reads the same rule. **Motion**, per `Charts.d.ts` → Motion: * ENTRANCE (once per mount, ChartEntranceMixin) — each rod GROWS from the baseline over `--t-move`, offset by its own day index's stagger step, so the window fills start to end. While that runs fl_chart's own implicit lerp is OFF: the mixin already rebuilds every frame with a fresh height, and a tween toward it would run a second clock over the same rods. * SETTLED — real values on the update clock (chartUpdateClock — `--t-move`, `--t-quick` under the dashboard bus), so a data change MORPHS through that implicit lerp instead of blanking and redrawing. A change arriving mid-entrance completes the entrance instantly and hands over to the morph. **Highlight** rides the enclosing unit's ChartHighlightScope, exactly as the line platter's does: while a label is raised, every OTHER labeled series drops to the ghost alpha. A series with no label is never raised and never dimmed, so the single-series posture is untouched. **The platter SCRUBS** (`Charts.d.ts` → Interaction: "SCRUB on continuous axes … raises the shared ChartTooltip: the bucket plus every series' swatch and value"; #788) in every mode. The bucket whose BAND holds the pointer takes a hairline indicator at its band center and raises the ChartTooltip — the day in the axis's own words, then one row per painted series (a single unnamed series is one value-only row; past six, six rows and "+N more", THE TOOLTIP PAST SIX ROWS). A crossing into a new bucket fires a selection haptic. It is a READ and commits nothing. Under a ScrubbableChartUnit, whose own scrub drives the headline, the platter stands its scrub down so one gesture never raises two readouts. **THE PLATTER'S GESTURES** (`Charts.d.ts`, 2026-10-02, #1081): a TAP reports the day whose band holds it through onDayTap, and the BRUSH (brush, onBrush, onBrushEnd) is TrendLine's, the same rect over the bars' plot. Which motion is which follows the pointer — on touch with a brush wired a horizontal drag scrubs and a long-press then drag brushes; on touch with none a long-press then drag scrubs; a mouse hovers to scrub and drags to brush — and none claims a vertical pan, so the board still scrolls through the plot (CHARTS NEVER BLOCK THE BOARD'S SCROLL). **DAILY TICKS** (dailyTicks, #1080): every day labeled, rotated past the band, and under kChartDayPitch the plot scrolls inside the platter, its value gutter pinned and its latest day in view. **THE BUCKET FOLLOWS THE BAND** (granularity, #786): a bucket may be a day, an ISO week or a calendar month, keyed by its first day. The platter never re-buckets: it spells its ticks, its card and its table by what it is told, and paints a TRAILING PARTIAL bucket (DayDatum.partialDays) at AppChartTheme.partialAlpha — every segment of its bar — its card adding "· 3 of 7 days so far". The web twin is `CountBars.tsx`.

CountBarPlatterdefault constructor

Knob Type Default What it does
axis ChartAxisMode
none · endpoints · adaptive
ChartAxisMode.adaptive The plot frame this platter draws for itself. Never inferred — a sparkline passes ChartAxisMode.none explicitly.
brush (double, double)? null The COMMITTED brush band (#1081), normalized 0–1 across the bars' plot (the whole scrolled plot under dailyTicks) — drawn as the static accent-tint band, exactly as `TrendLinePlatter.brush`.
crosshairDay DateTime? null THE HOVERED DAY IS SHARED (`Dashboard.d.ts`, 2026-10-14, #1428): a day another surface is reading — on a board, the bus's hovered day. The platter draws its CROSSHAIR ALONE at the band center of the bucket holding it (a week or a month holds every day in it): no tooltip and no announcement, while no…
dailyTicks bool false DAILY TICKS (`Charts.d.ts`, 2026-10-02, #1080): under ChartAxisMode.adaptive, EVERY day carries its tick label — flat while the widest label plus 12 fits a band, else rotated −45° and end-anchored under its day; and where a band would fall under kChartDayPitch the plot takes `days × kChartDayPitch`…
granularity ChartGranularity
day · week · month
ChartGranularity.day What each bucket spans (`Charts.d.ts` THE BUCKET FOLLOWS THE BAND, 2026-10-14, #786): ChartGranularity.day by default; a week or a month keys each DayDatum by its first day. The platter never re-buckets — it spells its ticks ("Sep 1 · 8 · 15", "Nov 2025 · Dec · Jan 2026"), its card's head ("Week of…
mode ChartBarMode
single · stacked · grouped
ChartBarMode.single How several series share a bucket — see the class doc. Default ChartBarMode.single.
onBrush ValueChanged<(double, double)?>? null The LIVE band while a brush is in flight — null while the span sits under the 3% floor, and null again on release.
onBrushEnd ValueChanged<(double, double)?>? null Enables the BRUSH — a long-press then drag on a coarse pointer, a press-drag on a fine one — and commits on release; a span under 3% of the plot commits null (clears the range). Null leaves it brush-free.
onDayTap ValueChanged<DateTime>? null THE PLATTER'S GESTURES (`Charts.d.ts`, #1081): a tap (a click) reports the CALENDAR DAY whose band holds the pointer — the bucket's own DayDatum.date, a coarser bucket's first day. It is the platter's primary tap: it raises no tooltip and no highlight. Null and a tap reports nothing.
reference ChartReference? null THE REFERENCE LINE (`Charts.d.ts` THE PLOT MAY CARRY THREE ANNOTATIONS, #1426): a target or threshold the host states — a 1 `ink60` line dashed 2,3 across the plot at its value, drawn OVER the gridlines and the bars and under the scrub's card, its words in THE END GUTTER at the plot's inline end.…
series * List<ChartBarSeries> — The series to plot. One entry is the single-series posture; an empty list is an empty window, which is a real state.
tapVerb String? null THE TOOLTIP NAMES THE TAP (`Charts.d.ts`, 2026-10-14, #1427): the verb a tap on the scrubbed bucket performs ("filter to this day"). While onDayTap is wired the card's LAST line names it — `Click to <verb>` at a fine pointer, `Tap to <verb>` on a coarse one, `Press Enter to <verb>` from the keyboard…
unit String? null The display-only unit of the values this platter spells (`Charts.d.ts` A VALUE'S UNIT): "12.5 L" on every gridline label and in the scrub tooltip, never on an x tick. A label, never a transform — the values are never rescaled. Null or empty reads the bare value, as before.

CategoricalBarsPlatter widget

The categorical horizontal-bars platter (`Charts.d.ts` → CategoricalBarsProps): top-N rows over a category vocabulary and a "N more" fold row for the folded tail. Pure Flutter — the right base for long open-enum labels (single-line ellipsis; the full string in the raised card and in semantics).…

Full dartdoc

The categorical horizontal-bars platter (`Charts.d.ts` → CategoricalBarsProps): top-N rows over a category vocabulary and a "N more" fold row for the folded tail. Pure Flutter — the right base for long open-enum labels (single-line ellipsis; the full string in the raised card and in semantics). **THE ROW IS ONE LINE** (`Charts.d.ts`, 2026-10-14, #1426, D8): a row is three columns — the LABEL (the body role, one line, ellipsized) in a column min(30% of the row, 140) wide and the same for every row; 10; the TRACK taking the rest; 10; the VALUE (the small data role in `ink40`, end-aligned) in a column as wide as the widest readout among the rows shown — so every track starts and ends at one x. The row is DesignTokens.touch tall (32 at a fine pointer, 44 on touch); under ChartBarMode.grouped it stacks one 12 rail per series, 2 apart, and grows to max(touch, that stack + 8). The value is the category's TOTAL under every mode ("subset/population" under highlightCounts). **THE FOLD READS "N MORE" AND TOGGLES.** Under topN the fold row's label reads "N more · a, b, c" and is a DISCLOSURE — a real button, `expanded: false`, named "N more: a, b, c" — that expands the fold in place: every category takes its own row in the platter's sort order, then a "Show fewer" row (the same disclosure, label column only) collapses it again. The fold row's track and value stay the fold's door and emit kCategoricalOther. Expanded or not is the platter's own view state, reset when topN or the category vocabulary changes. A FOLD OF ONE IS NOT A FOLD (#1425): a lone folded category with a value draws as its own row at the fold's end. **Color is the tier's, never the caller's.** A series' marks paint AppChartTheme.seriesColorAt of its own `series` position, defaulting to its index; there is no color parameter to pass. A series that carries a `tone` paints AppChartTheme.toneColor of it instead, in its fills and its card swatch (A SERIES MAY WEAR A STATUS TONE, #1425). **mode says how several series share a track** (`ChartBarMode`, #777): ChartBarMode.stacked lays each series' segment end to end on ONE rail, AppChartTheme.stackGap apart, so a track's length is its category's WHOLE — the thing a filter tap selects; ChartBarMode.grouped gives each series its own rail within the row. ChartBarMode.single with more than one series is a CALLER ERROR — see the constructor's assert. Values need not be whole: a Sum or an Average reads as the tier prints one (formatChartValue). **One vocabulary, one ranking.** The rows are the FIRST series' categories (a category the first series does not list has no row, so a host lists the union in the first series), and every series follows one order. sort's magnitude order and topN's membership rank by the category's RANK MAGNITUDE: the ROW TOTAL across the painted series under stacked and grouped, the first series' value under single. `applied`, `highlightCounts` and `onToggle` speak for the row, i.e. for the first series' vocabulary. **Sort and fold are separate questions.** sort is the READING order; topN is a magnitude reduction whose membership ranks by magnitude under every sort (foldTopN), and the fold row renders LAST in every order. The fold SUMS each series' folded values. NO legend, ever — the unit's legend slot carries one when the host builds it (a split's series chips); a single-series count bar's legend is one redundant entry the row labels already spell out (D8/Δ7). Highlight rides the unit's ChartHighlightScope: while a series label is raised, every OTHER labeled series' segments drop to the ghost alpha. **Δ3 read/drill, reconciled with the v2 chrome.** A categorical bar has no continuous axis, so it reads on TAP (and on a fine pointer's HOVER, THE ROW IS ONE LINE), never on scrub (Δ2 scope). What a tap does depends on what the row IS: * a LIVE DRILL DOOR (onDrill set, clickThrough true, no onToggle — the origin `DisableClickThrough: false` case) plants the row-count chip under the row and the chip drills. * anything else raises the shared ChartTooltip in the wrap posture — the row's caption plus every series' swatch and value. The card hangs from the row's bottom edge, which is where a planted chip would be; a hover card stands down on the row whose chip is planted. A second tap on the same row clears whichever was raised. **A ROW THAT ACTS IS A BUTTON** (`Charts.d.ts` THE PLOT TAKES THE KEYBOARD's categorical clause): a row with a door (onToggle, or a live onDrill) is a real focusable button — Enter or Space acts as its tap, the house focus ring (1 accent, zero offset) shows it, and its semantics node is a button carrying `onTap` (and `toggled` while its value is applied, under onToggle). A row with no door is not a stop; its words read in order. **Motion**, per `Charts.d.ts` → Motion: * ENTRANCE (once per mount, ChartEntranceMixin) — each track's fills grow from zero over `--t-move`, offset by its own ROW index's stagger step so the list fills top to bottom. The fold row is a row and takes the next step: the stagger explains a mark's POSITION in the list, not its data-ness. * MORPH — a width change tweens on the platter's UPDATE clock (chartUpdateClock): `--t-move`, so a data change slides rather than snaps — and `--t-quick` while the dashboard filter bus drives the platter, the population fills and their pending caps included (#741). The highlight OVERLAY tweens on `--t-quick` in every posture; a data change arriving mid-entrance completes the entrance instantly and hands over.

CategoricalBarsPlatterdefault constructor

Knob Type Default What it does
applied Set<String> const {} The applied values on the emitting dimension (`Charts.d.ts` `selected`) — THE CROSS-FILTER STATE (Contained, #895): while non-empty, an applied row keeps its series color (the lead series, the accent, on a single-series platter) and EVERY OTHER row STEPS BACK — its fill DesignTokens.ink20 (every…
clickThrough bool true Whether a tapped row opens a drill door (Δ3 / origin `DisableClickThrough`). Look-only plants no door even when onDrill is provided — it raises the readout card instead.
highlightCounts Map<String, num>? null Highlight-mode subset values per category (`Dashboard.d.ts`): present, the full-population track STEPS BACK to DesignTokens.ink20 with the lead-series overlay (ramp s0) at the subset width and the readout reads "subset/population" — nothing ever disappears. The FOLD sums its members' entries into…
mode ChartBarMode
single · stacked · grouped
ChartBarMode.single How several series share a track — see the class doc. Default ChartBarMode.single.
onDrill ValueChanged<CategoryDrill>? null Fires the slice-bound drill when a live door is tapped; `null` on the look-only bars. "Other" carries the complement-predicate inputs (T12).
onHover ValueChanged<String?>? null The transient hover/press report — the hovered value, null on exit; commits nothing (Dashboard.d.ts transient contract).
onToggle ValueChanged<String>? null The dashboard linkage (`Dashboard.d.ts`): when set, a row tap TOGGLES the value in the emitting dimension instead of planting a door — a bar is a filter door on the plate, never both. The fold row emits the literal `Other`.
series * List<ChartCategorySeries> — The series to plot. One entry is the single-series posture; an empty list is an empty window, which is a real state.
sort ChartCategorySort
magnitude · alphabetical · source
ChartCategorySort.magnitude The READING order of the rows. Never the fold's order — see the class doc.
tapVerb String? null THE TOOLTIP NAMES THE TAP (`Charts.d.ts`, 2026-10-14, #1427): the verb a DOOR row's tap performs ("filter to this value"). The card a row raises closes on it — `Click to <verb>` at a fine pointer, `Tap to <verb>` on a coarse one, `Press Enter to <verb>` from the keyboard. A row with no door…
topN * int — The fold budget (Δ6) — past this many rows the tail folds into ONE "N more" row, ranked by magnitude (the class doc) — never a fold of one.
unit String? null The display-only unit of the values this platter spells (`Charts.d.ts` A VALUE'S UNIT): "12.5 L" on every row's readout and in the raised card, "18/68 L" in highlight mode, and spoken per clause ("12 L, 2 L pending"). Never on a category label, a series name or the drill chip, which counts records.…

DonutPlatter widget

The status-donut platter (`Charts.d.ts` → DonutProps): ring sections in the supplied closed-enum order, colored through each segment's StatusTone (null falls through to the categorical series ramp), and the one legend the tier keeps. Zero-value segments leave the ring but stay in the legend, so the…

Full dartdoc

The status-donut platter (`Charts.d.ts` → DonutProps): ring sections in the supplied closed-enum order, colored through each segment's StatusTone (null falls through to the categorical series ramp), and the one legend the tier keeps. Zero-value segments leave the ring but stay in the legend, so the legend never reshuffles (stable legend, closed-enum order). **The legend is ChartLegend in VISIBILITY mode**, and the platter is the caller that owns `hidden`. Toggling a chip is visual only — never a data change — but it does change the WHOLE: a hidden segment leaves the denominator, so the survivors re-proportion (in place, on `--t-move`) to fill the ring. That is the reason the donut takes the visibility mode rather than the highlight one. **Which means the ring, not a chip, is the LIFT's door.** `Charts.d.ts` → Interaction reads "tapping a legend chip or a mark highlights … the donut's active segment lifts", and in a highlight-mode legend the chip is one of those doors — but this platter's chips are spending their tap on visibility. So a tap on the RING hit-tests to a span and writes the enclosing unit's ChartHighlightScope (label-matched, second tap clears), and the raised span paints a lift wider and thicker than its siblings. Outside a unit the ring tap is inert: there is no channel to write. **A tone-keyed platter supplies glyphs.** Color here is the app's status mapping rather than a ramp position, and a hidden chip's own treatment is color-only (the specimen's `.fo-legend__chip--off` drops the word to ink40 and ghosts the swatch — no shape change). DonutSegment.glyph is what carries the reading when color cannot, so pass one per segment. **Geometry is the specimen's wrapping row, not a column.** `.fo-donut` is `display:flex; gap:18px; align-items:center; flex-wrap:wrap` over a `flex:none` ring plate at the COMPACT plot measure on both axes (`AppChartTheme.plotHeightCompact`), with the legend taking what is left beside it and dropping under it when there is no longer room. The ring never GROWS with the tile — same proportions, same reading, whatever box a host hands over — but the measure is a cap, not a floor: a host too short to seat it shrinks the plate rather than overflowing, and the stacked posture gives the legend its height first so the plate resolves against what is left. That resolved size is read once and fed to the section gap, the painter and the hit-test together, so the three cannot disagree. **Motion**, per `Charts.d.ts` → Motion: * ENTRANCE (once per mount, ChartEntranceMixin) — the whole ring sweeps clockwise from 12 o'clock as ONE reveal over `--t-draw`. The donut is a single traced path, so there is no per-segment stagger to key. * CHANGE — a data change or a visibility toggle re-proportions IN PLACE: the span layouts tween old → new over the update clock (chartUpdateClock — `--t-move`, `--t-quick` under the dashboard bus), matched by label, so an entering segment grows from nothing and a departing one shrinks out. A change arriving mid-entrance completes the entrance instantly and hands over to the morph.

DonutPlatterdefault constructor

Knob Type Default What it does
center DonutCenter? null The total-in-the-hole. Absent leaves the hole empty.
legend bool true Whether the legend renders. On by default — the chips are the only place a segment's word and count are stated.
segments * List<DonutSegment> — Ordered closed-enum segments.

AgingStripPlatter widget

The open-work aging platter (`Charts.d.ts` `AgingStrip`): one horizontal proportional track (bar-track height 12, ends at rXs, 2px gaps between TRUE segments like the donut) whose segments are tone-filled by age, over a legend row of chips — segment word + data-font count, the non-color channel (SC…

Full dartdoc

The open-work aging platter (`Charts.d.ts` `AgingStrip`): one horizontal proportional track (bar-track height 12, ends at rXs, 2px gaps between TRUE segments like the donut) whose segments are tone-filled by age, over a legend row of chips — segment word + data-font count, the non-color channel (SC 1.4.1). Zero-count segments vanish from the track but KEEP their legend chip at the — mark, so the bucket vocabulary stays stable. Legend chips are drill doors when onDrill is wired — the hit target is the full-height chip (≥44), never the 10px track. **The chips WRAP in a row** (the specimen's `.fo-aging__chips`, a `flex-wrap` row at an 18 gap), each hugging its own width at the 44 touch height — never one chip per line (#1428: each chip took the legend's whole width, so four buckets stacked ~180 tall and the strip could not fit a `ChartUnit`'s 180 slot). The strip then costs 12 + 12 + 44 per chip line, so four buckets sit in one line on a desktop unit and two on a phone, inside the slot either way. ONE semantics node summarizes the buckets, carrying a per-segment custom action when onDrill is wired — never a second node per chip. **Motion**, per `Charts.d.ts` → Motion: two clocks, never one. * ENTRANCE (once per mount, ChartEntranceMixin) — each segment's fill grows from zero INSIDE its own slot over `--t-move` (a mark growing in place, the specimen's `fo-grow-x` on `.fo-track__fill`), offset by its DRAWN-SLOT stagger step so the strip fills start to end. The slots themselves never move during the entrance — a fill grows within the width it is going to keep, so nothing slides sideways under the reader. **The stagger explains position among the marks that are DRAWN.** Here a zero-count bucket vanishes from the strip (`Charts.d.ts` → AgingStrip: "Zero-count segments vanish from the strip but KEEP their legend chip"), so it has no position for a stagger to explain and consumes no step — keying it on the bucket would spend a beat on an empty strip. That is the opposite of DayDatum.synthetic's ruling in `CountBars`, and the two agree on the principle rather than on the key: a synthetic DAY holds its x slot on the axis, so it holds its step; an empty age bucket holds nothing. Slots when zero entries occupy them, drawn marks when they don't. * MORPH — a data change tweens the segment SHARES on the update clock (chartUpdateClock — `--t-move`, `--t-quick` under the dashboard bus; the specimen transitions `flex-grow`), so a filter recompute re-proportions in place rather than blanking. A change arriving mid-entrance completes the entrance instantly and hands over. Reduced motion settles on the first paint and the wrappers stay out of the tree entirely, so the settled tree is byte-identical to the un-animated one — which is also the golden path.

AgingStripPlatterdefault constructor

Knob Type Default What it does
onDrill ValueChanged<String>? null Legend chips become drill doors; null renders inert readouts.
segments * List<AgingSegment> — The ordered buckets (the APP's vocabulary, youngest first).
semanticsLabel * String — The one spoken summary prefix ("Open action items by age").

ChartHero widget

The dashboard's KPI hero (`Charts.d.ts` `ChartHero`) — the ONE composition a dashboard leads with: the RingGauge beside the kpi-role headline (value + optional delta on the shared baseline) with the StatRow beneath it, and an optional full-width area trend platter (compact plot height) closed by…

Full dartdoc

The dashboard's KPI hero (`Charts.d.ts` `ChartHero`) — the ONE composition a dashboard leads with: the RingGauge beside the kpi-role headline (value + optional delta on the shared baseline) with the StatRow beneath it, and an optional full-width area trend platter (compact plot height) closed by endpoint labels. One number said twice (arc + numeral), never two different numbers, so the gauge takes RingGaugeCenter.none — a center numeral made the hero say it three times (#1428). A null value renders the — mark AND forces the gauge's no-data state — no sweep, the neutral ink20 ring — whatever ratio and band say (a no-data window is never a fake percentage, `Charts.d.ts` ChartHero). THE BAND LABEL IS SEEN (`Charts.d.ts` ChartHero, #1428): bandLabel is drawn under the headline, beside the gauge, in the label role at ink60, one line, ellipsized — AND it joins the one spoken node. A status is a word, never color alone: a band read only off the ring's tone is the color-only reading the house bans. ONE semantics node summarizes label + value + delta + band; the gauge and the trend are excluded under it. Morph adaptation: compact stacks the gauge above the headline band; medium/expanded run them inline. **The trend slot keeps the ENDPOINTS posture** (`Charts.d.ts` → Axes: "the hero trend's posture"). The host constructs it with `axis: ChartAxisMode.endpoints`, so the platter draws its y gridlines and NO x ticks, and the x frame is this widget's own startLabel/endLabel pair below it — the endpoint-label anatomy the hero has always carried. Adaptive would put a tick row where those labels sit and say the same thing twice; `none` would leave a bare sparkline under a hero headline. The zero line follows from the same clause, and the hero DRAWS IT ITSELF. Under endpoints the platter closes no plot (`ChartFrame.ownsBaseline` is adaptive-only), and the hero is not a ChartUnit — it hosts the platter directly — so nothing else would paint the hairline the spec's own hero anatomy requires ("closed by the baseline rule + endpoint labels"). The rule is therefore part of the TREND block: no trend, no rule.

ChartHerodefault constructor

Knob Type Default What it does
band * StatusTone
positive · info · caution · critical · neutral
— The gauge band tone — the APP decides the thresholds. Not read when value is null (the neutral band, always).
bandLabel * String — The BAND clause only ("Safe band", "no decided answers in this period"), drawn under the headline AND spoken (THE BAND LABEL IS SEEN) — the label, value and delta come from this widget's own props, so a clause repeating them would say the number twice.
delta ChartDelta? null The resolved period-over-period delta, a figure on the headline's baseline (THE DELTA IS A FIGURE).
deltaCaption String? null The comparison-window caption ("vs prior 30d").
endLabel String? null End endpoint label under the trend.
label * String — The hero's label, sentence case ("Safe ratio"; `ChartUnitProps.label`).
ratio * double — The gauge ratio in 0..1. Not read when value is null: the gauge renders its no-data state — as it does for a non-finite ratio (NaN or ±infinity, a `done / total` over no total), which RingGauge reads as no data.
startLabel String? null Start endpoint label under the trend.
stats * List<StatCellData> — The stat cells under the headline.
trend Widget? null Optional area trend beneath the band — a trend platter node.
value * String? — The pre-formatted kpi headline; null renders the — mark.

RingGauge widget

The KPI hero ring gauge (Δ8) — a flat CustomPainter arc on a fixed 0–100% scale (Apple's battery case: the band semantics stay readable on a stable scale). Replaces the origin's 5.7 KB `vc_svg_metrics` SVG ring; its Gaussian-blur glow is CUT, not substituted — the band color already IS the state…

Full dartdoc

The KPI hero ring gauge (Δ8) — a flat CustomPainter arc on a fixed 0–100% scale (Apple's battery case: the band semantics stay readable on a stable scale). Replaces the origin's 5.7 KB `vc_svg_metrics` SVG ring; its Gaussian-blur glow is CUT, not substituted — the band color already IS the state (token accounting stays closed). **Motion**, per `Charts.d.ts` → Motion: two clocks, never one. * ENTRANCE (once per mount, ChartEntranceMixin) — the arc fills 0→ratio over `--t-draw`. It is a TRACED path, not a mark growing in place: the specimen animates it with `fo-sweep` on the draw band, alongside the KPI numeral counting up beside it ("the gauge fills while the KPI numeral counts up"), so the two land together. `traceProgress(0)` is the whole stagger — a gauge is one mark, and there is nothing to offset it against. * MORPH — a later ratio change tweens old→new on the update clock (chartUpdateClock — `--t-move`, `--t-quick` under the bus); a change arriving mid-fill completes the entrance instantly and hands over, so two clocks never fight over the same arc. Its anatomy is `Charts.d.ts` RingGauge's, stated whole (#1428): * ONE GAUGE SIZE — size 128 with a ring AppChartTheme.barTrackHeight (12) wide, the specimen's geometry. A thin ring is the house's crisp-mark rule, and a gauge is not a touch target, so the donut's ≥24 touch ring does not bind it. size scales the diameter; the ring stays 12. * THE ARC IS CRISP — it sweeps clockwise from 12 o'clock in the band's tone over the DesignTokens.ink4 track, its ends BUTT: a round cap added half the ring's width at each end, overstating a 0.32 share and drawing a dot at 0%. A ratio of 0 paints no arc. * A NULL GAUGE SAYS SO — a null ratio sweeps nothing: the WHOLE ring paints the neutral band, the stepped-back DesignTokens.ink20 (NEUTRAL IS --ink20 FOR EVERY MARK, #1425), and the center reads "—" in the compact value role at ink60. band is not read: a no-data window has no band to tone. No data is never a fake 0% or 100%. A NON-FINITE ratio — NaN or ±infinity, the `done / total` of an empty window — is no data exactly as null is, never a throw. * THE CENTER NUMERAL — center. RingGaugeCenter.value (the default) draws the ratio rounded to a whole percent ("42%") in the compact value role at ink, counting up with the arc and landing on it; RingGaugeCenter.none draws nothing there. A null gauge's "—" follows the same knob. `ChartHero` passes none: its headline already states the value, and a center numeral made the hero say it three times. The gauge CENTERS ITSELF at size in whatever box it is given, so a host passes it bare — a `ChartUnit` reads it as a kit platter only when it is the plot itself, and a wrapper hid it from the unit's NO RULE WHERE THERE IS NO VALUE AXIS check. Reduced motion settles on the first paint, which is also the golden path.

RingGaugedefault constructor

Knob Type Default What it does
band * StatusTone
positive · info · caution · critical · neutral
— The Δ8 band role — resolves to the active status palette pair. Not read when ratio is null (the neutral band, always).
center RingGaugeCenter
value · none
RingGaugeCenter.value THE CENTER NUMERAL: RingGaugeCenter.value draws "42%" (or a null gauge's "—") at the center; RingGaugeCenter.none draws nothing there.
ratio * double? — The safe ratio in 0..1 (fixed full-circle scale); null — or a non-finite ratio, NaN or ±infinity — is NO DATA: the neutral ring and the — mark, never a fake 0%.
semanticsLabel * String — Spoken summary including the band *name* ("Safe ratio 74%. Caution band.") — color is never the sole channel.
size double 128 The gauge diameter, 128 by default (ONE GAUGE SIZE); the ring stays 12 at any size.

StatRow widget

The KPI-hero stat row (Δ13): a row of count-up stat cells sized to its **intrinsic** content — the fix for the shipped fixed 140×136 box that clipped at large text scale. Cells share the tallest cell's height (`IntrinsicHeight`) and the whole row grows with the text scaler rather than truncating (SC…

Full dartdoc

The KPI-hero stat row (Δ13): a row of count-up stat cells sized to its **intrinsic** content — the fix for the shipped fixed 140×136 box that clipped at large text scale. Cells share the tallest cell's height (`IntrinsicHeight`) and the whole row grows with the text scaler rather than truncating (SC 1.4.4). Each cell is one merged semantics node (label + final value; the animated digits are excluded).

StatRowdefault constructor

Knob Type Default What it does
cells * List<StatCellData> — The stat cells, rendered in order with equal flex.

StatValue widget

The Δ8 stat count-up: an `IntTween` 0→value, rendered tnum (digits never jitter) and T16-grouped at every frame. **Motion**, per `Charts.d.ts` → Motion: two clocks, never one. * ENTRANCE (once per mount, ChartEntranceMixin) — the numeral counts up 0→value over `--t-draw`, the band the gauge fills…

Full dartdoc

The Δ8 stat count-up: an `IntTween` 0→value, rendered tnum (digits never jitter) and T16-grouped at every frame. **Motion**, per `Charts.d.ts` → Motion: two clocks, never one. * ENTRANCE (once per mount, ChartEntranceMixin) — the numeral counts up 0→value over `--t-draw`, the band the gauge fills on, because the constitution choreographs them as ONE moment ("the gauge fills while the KPI numeral counts up"). `traceProgress(0)` is the whole stagger — one numeral has nothing to be offset against. * MORPH — a later value change tweens old→new on the update clock (chartUpdateClock — `--t-move`, `--t-quick` under the bus); a change arriving mid-count completes the entrance instantly and hands over. **The count lands on the DISPLAY-ROUNDED value by construction.** value is an `int` and IntTween interpolates to exactly it, so the count and a ChartDelta reading the same rounding can never disagree — there is no rounding decision to keep in step, which is what the constitution's clause asks for. Reduced motion renders the final value on the first paint (the golden path). Semantics ALWAYS carry the final value — a screen reader must never speak a transient count (the animated text is excluded; the merged node reads "{label}: {final}").

StatValuedefault constructor

Knob Type Default What it does
compact bool false Compact renders at `kpiValueCompact` (22) instead of 32.
semanticsLabel String? null The stat's spoken label ("Observations"); null speaks the bare value.
value * int — The target count (backend-owned counter, T11 — pre-aggregated).

KpiValue widget

THE KPI (`Charts.d.ts` THE KPI / `StatValueProps`, Contained, 2026-09-29, #895 — ONE anatomy wherever a KPI renders: a dashboard READOUT tile, a stat card, a KPI band): * THE LABEL LINE — label in kpiLabelStyle, sentence case, one line ellipsized. Omitted inside a readout tile, which draws the…

Full dartdoc

THE KPI (`Charts.d.ts` THE KPI / `StatValueProps`, Contained, 2026-09-29, #895 — ONE anatomy wherever a KPI renders: a dashboard READOUT tile, a stat card, a KPI band): * THE LABEL LINE — label in kpiLabelStyle, sentence case, one line ellipsized. Omitted inside a readout tile, which draws the tile's title there (THE FRAME PADS, KitKpiPostureScope). * THE VALUE — the pre-formatted value in the kpi role, tabular, ink, line-height 1; never semantic color. The ladder: 32 at two rows or more, kKpiInlineRung at one, 22 at two columns or fewer or when compact — at every density. Under height pressure the numeral AUTOFITS (scale-down, never ported advance arithmetic) to the height its posture leaves it (#938): a compact one-row readout starts at the 28 rung and shrinks into the 68 its card leaves, where the interim kit started it at 22. * THE DELTA — delta + deltaCaption as a ChartDeltaFigure (THE DELTA IS A FIGURE): the one semantic color in a KPI. * THE TREND — trend, a 92 × 34 sparkline of the window FOLLOWING the value block at a 16 gap, bottom-aligned with it, the rest of the line empty (THE TREND STAYS BY ITS VALUE, #1424), DRAWN BY THE SERIES-LINE RULES (`Charts.d.ts`, 2026-10-14, #1424, D9): the series stroke (2) in THE LEAD SERIES (`DesignTokens.chartRamp0`), straight, spaced by the CALENDAR (every day from the first point to the last at its own place), its readings JOINED across any day with no reading — always, with no knob (A GAP IS JOINED BY DEFAULT, #1427), so a window of one reading draws only its square — the latest point the 6 square in the same color; no area, no axes, no per-reading marks. Still scaled MIN → MAX over its own window, inset by half the end square: a KPI trend shows shape, the value beside it the magnitude. The lead series whatever the KPI measures — the delta already carries the news. * THE STATUS — status in statusTone on the label line, 8 after the label (KpiLabelLine): 'blocked' is the needs-action tag. Red never reaches the value. Inside a readout tile the TILE draws it (the tile's title is the label line), so a KpiValue there takes none. TWO POSTURES (posture, else the frame's KitKpiPostureScope, else stacked): STACKED pads 14 / 16 and puts the delta line 8 beneath the value; INLINE pads 10 / 16 and seats the delta 8 beside the value on its baseline. WHAT YIELDS ON WIDTH: kpiWidthPlan — the trend narrows, then the caption drops while the trend holds 48, then the trend drops (#1428). The transient highlight-mode ghost twin rides in accent beside the numeral, never persisted and never spoken. onTap makes the whole KPI a real pressable (the dashboard filter door); ONE semantics node speaks the label, the value and the delta (`chartSummary`).

KpiValuedefault constructor

Knob Type Default What it does
compact bool false Renders the numeral at the kpi-c rung (22) whatever the posture.
delta ChartDelta? null THE DELTA; null renders none.
deltaCaption String? null The delta's quiet caption ("vs prior 30 days").
ghost String? null The transient accent twin ("18") shown during a hover/press elsewhere on the plate; null renders the numeral alone.
label String? null THE LABEL LINE's text. Omitted inside a readout tile, whose title is the label line — a label passed there would name the KPI twice, and asserts in debug.
onTap VoidCallback? null The dashboard filter-door gesture (Dashboard.d.ts: "tap the Blocked KPI → toggle that value in its dim"). Non-null makes the KPI a REAL pressable — button semantics, focus, the state contract; null renders the plain readout.
posture KpiPosture?
stacked · inline
null The posture; null reads the frame's KitKpiPostureScope, else stacked.
semanticsLabel String? null The spoken label; null speaks label (or, in a readout tile, the bare value — the tile's own node names it).
status String? null THE STATUS's word (`Charts.d.ts` THE KPI, #948) — drawn on the label line and spoken after the label. Null draws none. Inside a readout tile the tile draws it from `DashboardTileContent.status`, and a status passed here asserts, exactly as label does.
statusTone AppStatusMarkTone?
pending · active · done · blocked
null THE STATUS's StatusMark tone — AppStatusMarkTone.blocked is the KPI's needs-action signal. Null draws status as the plain word.
trend List<(DateTime, double)>? null THE TREND — the window's points, drawn as the lead-series sparkline. Null (or empty) renders no trend and reserves no width; ONE point draws only its square (A GAP IS JOINED BY DEFAULT, #1427).
value * String — The pre-formatted headline value ("2h 42m", "57%").

SparklineRow widget

The sparkline-row unit (third anatomy — phone-stack tail density): label + compact tnum value + delta figure + a trailing sparkline plot, `sparklineHeight` (40) tall, in one row. The whole row is ONE tap target → the expanded tier (platter rule: no mixed tap zones); no scrub, no legend, no footer at…

Full dartdoc

The sparkline-row unit (third anatomy — phone-stack tail density): label + compact tnum value + delta figure + a trailing sparkline plot, `sparklineHeight` (40) tall, in one row. The whole row is ONE tap target → the expanded tier (platter rule: no mixed tap zones); no scrub, no legend, no footer at this height — the expanded tier is one tap away and carries all of it. Semantics merge to one node (Δ9); the granular path is the expanded tier. WHAT YIELDS WHEN THE ROW IS SHORT OF ROOM (`Charts.d.ts` SparklineRow, 2026-09-25; #663). THE VALUE NEVER YIELDS: the numeral is laid out at its own width and never ellipsized, clipped or squeezed, because a cut number misstates the datum. * WIDTH yields in a fixed order (sparklineRowWidthPlan). First the plot narrows from 96 toward 48 whenever keeping it would leave the label under 64; then the delta's CAPTION drops (the figure stays, as the value does) while the plot holds its 48; only then does the plot drop whole; then the label ellipsizes. A label narrower than 64 is protected at its own width — the plot never gives up room the label cannot use. The caption yields BEFORE the plot drops (#1428): the plot is what makes this row a sparkline row, and a phone row that dropped it while "vs prior 7 days" stayed kept the least of what it says. (`Charts.d.ts` SparklineRow still orders the plot's drop before the delta's; the bench finding moves it, and the clause owes the same amendment upstream.) * HEIGHT (sparklineRowHeightPlan): the row is at least 56 tall, on the specimen's 4 block padding — a 40 plot row is exactly 56. No inline padding (the specimen's `padding-inline: 0`): the LIST host insets its rows, and a row that padded itself spent 56 of a phone's width on nothing. A SparklineRow is a LIST row only (Contained, 2026-09-29, #895 — SUPERSEDES the 2026-09-25 clause that a row which is a dashboard tile's WHOLE BODY fills it, and the whole-body fill path is DELETED): a one-readout tile is THE KPI (`KpiValue`) in a READOUT tile, whose label line, value, delta and trend are this row's parts re-laid for a card. A bound taller than the row centers the content in a larger single tap target.

SparklineRowdefault constructor

Knob Type Default What it does
delta ChartDelta? null The resolved delta, a figure beside the value on its baseline (THE DELTA IS A FIGURE); null renders none.
deltaCaption String? null The comparison-window caption ("vs prior 30d").
label * String — The metric label (muted, leading).
onOpen * VoidCallback — Opens the expanded tier — the row's single affordance.
plot * Widget — The sparkline plot (a bare line platter). Excluded from semantics. It narrows to 48 and then drops under WIDTH pressure.
populationPlot Widget? null The highlight-mode population reference (`Dashboard.d.ts`): a quiet mark drawn UNDER plot — the caller paints it STEPPED BACK, in DesignTokens.ink20 (THE CROSS-FILTER STATE, #895; it was the hairline role) — so the lead-series subset rides over a never-vanishing whole. Null renders the plot alone.…
value * String — Pre-formatted (T8/T16) headline value. Never yields (see the class doc).

ChartDeltaFigure widget

THE DELTA IS A FIGURE (`Charts.d.ts`, Contained, 2026-09-29, #895 — the delta StatusPill, its trend glyph and the 12px delta-glyph geometry are DELETED). A delta is ONE figure and ONE caption, never a pill: * the FIGURE is the signed, display-rounded magnitude (chartDeltaFigureText — "+3", "−2",…

Full dartdoc

THE DELTA IS A FIGURE (`Charts.d.ts`, Contained, 2026-09-29, #895 — the delta StatusPill, its trend glyph and the 12px delta-glyph geometry are DELETED). A delta is ONE figure and ONE caption, never a pill: * the FIGURE is the signed, display-rounded magnitude (chartDeltaFigureText — "+3", "−2", "+12%") in the data role at the em weight (DesignTokens.dataEm), and it is the ONLY part of a readout that takes semantic color: DesignTokens.danger when the app's sentiment is worse, DesignTokens.ok when better, DesignTokens.ink when absent — and ink for a FLAT delta whatever the sentiment says (no change is nobody's news); * the CAPTION (caption, "vs prior 30 days") follows one space after, in the UI font at the label size, DesignTokens.ink40. showCaption false drops it (a KPI short of width drops the caption before anything else it keeps — the figure never yields); * the no-baseline delta renders no figure: the caption slot reads noBaselineLabel ("No prior period") in ink40. Placement is each host's: beside the value on its baseline (ChartUnit, ChartHero, SparklineRow, an inline KPI) or beneath it (a stacked KPI). The widget reports a real alphabetic baseline, so a baseline-aligned `Row` seats it on the numeral's line. ONE semantics node speaks chartDeltaPhrase — "up 3, worse, vs prior 30 days" — because the color carries the sentiment for a sighted reader only. A host that speaks the delta in its own merged summary (every chart unit does, through `chartSummary`) excludes this node beneath it.

ChartDeltaFiguredefault constructor

Knob Type Default What it does
caption String? null The comparison-window caption ("vs prior 30 days").
delta * ChartDelta — The resolved delta.
noBaselineLabel String kDeltaNoBaselineLabel The no-baseline caption — the kits' shared default copy.
showCaption bool true Whether the caption renders. The figure always does.

DrillChipView widget

The planted row-count chip (Δ3): a ≥48 dp door that drills on tap when onDrill is non-null (click-through enabled), or an inert readout when look-only (`DisableClickThrough: true` on 103/104). The chevron ` ›` is drawn only on the live door — the look-only readout carries no door affordance.

DrillChipViewdefault constructor

Knob Type Default What it does
onDrill * VoidCallback? — Fires the drill; `null` = look-only (no door, no chevron).
text * String — The pre-formatted chip text (formatDrillChip); the caller does not render this widget when the text is null.

ChartEmptyGhost widget

The empty tint: DesignTokens.ink40 marks at AppChartTheme.chartDimAlpha (`--chart-a-dim`, 0.30) behind the empty frame's CTA block — `Charts.d.ts` ONE GEOMETRY, TWO TINTS: "the EMPTY ghost is --ink40 at --chart-a-dim" (the specimen's `.fo-sil`). Decorative: hidden from assistive tech, because the…

Full dartdoc

The empty tint: DesignTokens.ink40 marks at AppChartTheme.chartDimAlpha (`--chart-a-dim`, 0.30) behind the empty frame's CTA block — `Charts.d.ts` ONE GEOMETRY, TWO TINTS: "the EMPTY ghost is --ink40 at --chart-a-dim" (the specimen's `.fo-sil`). Decorative: hidden from assistive tech, because the empty frame's message says what is empty.

ChartEmptyGhostdefault constructor

Knob Type Default What it does
shape * ChartSilhouette
bars · area · donut · ring · sparkline
— The chart-type ghost shape to draw.

ChartLoadingSilhouette widget

The loading tint: the silhouette's marks in DesignTokens.hair at FULL opacity, so a screen still loading never reads as a screen with nothing in it (distinct from empty by construction — Δ12). THE LOADING SILHOUETTE (`Charts.d.ts` `ChartSilhouette`, #1428): it is ONE IMAGE NAMED "Loading" — the only…

Full dartdoc

The loading tint: the silhouette's marks in DesignTokens.hair at FULL opacity, so a screen still loading never reads as a screen with nothing in it (distinct from empty by construction — Δ12). THE LOADING SILHOUETTE (`Charts.d.ts` `ChartSilhouette`, #1428): it is ONE IMAGE NAMED "Loading" — the only thing where the chart will be, so it says what is happening. One `Semantics(image: true, label: 'Loading')` node over the painter; the empty ghost stays decorative.

ChartLoadingSilhouettedefault constructor

Knob Type Default What it does
shape * ChartSilhouette
bars · area · donut · ring · sparkline
— The chart-type ghost shape to draw.

ChartTileErrorSurface widget

The Δ18 contained per-tile failure surface: one tile's own slice query threw while its siblings resolved. The frame + baseline hairline stay (the same non-collapse contract as loading/empty); the plot region shows a compact "Couldn't load" line + a **Retry** that re-subscribes **only this tile's…

Full dartdoc

The Δ18 contained per-tile failure surface: one tile's own slice query threw while its siblings resolved. The frame + baseline hairline stay (the same non-collapse contract as loading/empty); the plot region shows a compact "Couldn't load" line + a **Retry** that re-subscribes **only this tile's stream** (a local Drift re-read, no screen reload). The KPI hero and healthy siblings keep rendering their real values. Styling is secondary-ink (`ink60`) copy + a `danger` glyph — never an alarm-red full-bleed. Distinct from empty (no CTA, no ghost — there may be data; the query just failed).

ChartTileErrorSurfacedefault constructor

Knob Type Default What it does
label * String — The tile's label — the semantics node swaps to "{label}: couldn't load. Double-tap to retry." so a SR user gets the same Retry.
onRetry * VoidCallback — Re-subscribes this tile's stream (local Drift re-read).
padding EdgeInsetsGeometry? null The surface's own padding. Null is the STANDALONE inset, DesignTokens.rowV × 2 all round. A host that already pads the surface passes EdgeInsets.zero: inside `AppChartTile`'s body, which is padded 12 / 16, the surface takes no padding of its own (`Charts.d.ts` THE CARD HEAD WINS OVER A PADDED CARD —…

EarnedStateCountdown widget

The earned-state countdown card (Δ8) — the honest "not enough history yet" surface for the trends section, **not** a bare empty chart and **not** the loading skeleton. Art direction is pinned: an icon + type, no illustration, no ghost chart.

Full dartdoc

The earned-state countdown card (Δ8) — the honest "not enough history yet" surface for the trends section, **not** a bare empty chart and **not** the loading skeleton. Art direction is pinned: an icon + type, no illustration, no ghost chart. THE EARNED STATE IS WORDS (`Charts.d.ts` `EarnedStateProps`, #1428): the count "k of N days" — k clamped to 0..N — is its one figure, in the data face at the em weight (DesignTokens.dataEm at the body size) in ink; the one caption names the caller's own N in DesignTokens.ink60.

EarnedStateCountdowndefault constructor

Knob Type Default What it does
daysAccrued * int — Days of `date_dim` history so far (the `k` in "{k} of {N} days").
unlockDays int kTrendsUnlockDays The unlock target (`N`).

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.

AgingSegment support

One age bucket on the AgingStripPlatter. label is the raw bucket name, byte-exact; tone ramps with age — the APP decides the thresholds and the vocabulary, never this widget.

Field Type Default What it does
count * int — Rows in the bucket.
label * String — The raw bucket label.
tone * StatusTone — The bucket's tone (ramping toward critical with age).

CategoryDrill support

The click-through drill request a tapped bucket emits (Δ3). The host turns it into a slice-bound `DataTableView` route: the byte-exact group-key predicate (T3) ∧ the security filter.

Field Type Default What it does
isOther bool false True for the folded "Other (k)" track — the drill is the complement predicate, never an equality on a value.
label * String — The tapped track's raw byte-exact label (T3). For the "Other" track this is the display caption — the host must key off isOther, never this string.
topLabels List<String> const [] The surviving top-N labels — the `NOT IN (...)` input for an "Other" drill.

ChartBarSeries support

One bar series over the SHARED zero-filled date domain (`Charts.d.ts` → ChartBarSeries). Every series is zero-filled to the SAME buckets before it reaches a platter — a bar chart whose series disagree about the x domain paints a lie, so unlike ChartLineSeries (whose points carry their own date and…

Field Type Default What it does
days * List<DayDatum> — The zero-filled calendar-day series. Synthetic days render flat and stay drill-inert (Δ5).
label String? null The legend chip's word, and the identity a shared highlight channel would raise this series by. Absent → the single-series posture. Wired to neither channel yet: both arrive with the stacked/grouped modes.
series int? null The ramp position this series paints at; absent → its index in the list. It is a POSITION, never a color — the ramp belongs to the tier.
tone StatusTone? null A STATUS TONE in place of a ramp position (`Charts.d.ts` A SERIES MAY WEAR A STATUS TONE, #1425): the series' marks, its legend swatch and its tooltip swatch paint the tone (`AppChartTheme.toneColor`; neutral is the stepped-back ink20), and its words never do. Present, series is ignored for paint.…

ChartCategorySeries support

One categorical series over the SHARED category vocabulary (`Charts.d.ts` → ChartCategorySeries). Grouped and stacked series name the same categories, so ordering stays ONE decision: `sort` and `topN` read the FIRST series' counts and every other series follows that order, rather than each arguing…

Field Type Default What it does
categories * List<CategoryCount> — The raw bucket set, byte-exact labels (T3). A label repeated in the list is ONE bucket — the first occurrence wins, exactly as the specimen renderer's per-series `Map` does.
label String? null The legend chip's word, and the identity a shared highlight channel would raise this series by. Absent → the single-series posture. Wired to neither channel yet: both arrive with the stacked/grouped modes.
series int? null The ramp position this series paints at; absent → its index in the list. It is a POSITION, never a color — the ramp belongs to the tier.
tone StatusTone? null A STATUS TONE in place of a ramp position (`Charts.d.ts` A SERIES MAY WEAR A STATUS TONE, #1425): the series' marks, its legend swatch and its tooltip swatch paint the tone (`AppChartTheme.toneColor`; neutral is the stepped-back ink20), and its words never do. Present, series is ignored for paint.…

ChartDelta support

The resolved delta (`Charts.d.ts` `ChartDelta`): direction, display magnitude and the app's sentiment — given, or derived from goodDirection. Rendering is `ChartDeltaFigure`'s (THE DELTA IS A FIGURE); the semantics live here so the visual figure and the Δ9 spoken trend word read the identical…

Field Type Default What it does
direction * DeltaDirection — Direction, read off the display-rounded delta — never the raw float.
goodDirection ChartGoodDirection? null THE GOOD DIRECTION (`Charts.d.ts`, #1425): which way is good for this measure. With no sentiment given, a direction matching it derives DeltaSentiment.better and the opposite DeltaSentiment.worse; flat and noBaseline derive none.
magnitudeText String? null The rounded magnitude at the metric's DecimalDigits, T16-grouped ("3", "12%"; a pre-signed "+12%" or "−2" is kept as given); `null` in the no-baseline state (no figure).
sentiment DeltaSentiment? null

ChartEmptyState support

The empty-frame CTA block (Δ12 — "CTA pattern, not apology"): one message line + a single accent action naming the write flow that would populate this chart's slice. The chart passes NO data — the action routes through the form/action canon (T14).

Field Type Default What it does
ctaLabel String? null The single accent text action. The CTA renders only with both it and onCta (`ChartUnitProps.empty`: `ctaLabel?`, `onCta?`) — a window with nothing to offer states its message alone.
ghost Widget? null The chart-type ghost silhouette (`ChartEmptyGhost`: ink40 at `chartDimAlpha`); null renders no ghost.
icon IconData? null Message glyph — the shared empty-state 24 (in-tile) tier (F4/F5); null renders none.
message * String — The pinned per-chart empty copy (bless-final microcopy).
onCta VoidCallback? null Fires the CTA — routes through the form/action canon (T14).

ChartLegendItem support

One legend chip (`Charts.d.ts` → ChartLegendItem).

Field Type Default What it does
color Color? null Overrides the ramp for a TONE-keyed platter (the donut's segments).
glyph KitIconRef? null The non-color channel (SC 1.4.1) — a shape beside the swatch where a platter's colors are tone-keyed. It ADDS to the swatch; it never replaces it.
label * String — The chip's word — always rendered, so the swatch is never the only channel a series is told apart by.
prior bool false THE LEGEND CHIP (`Charts.d.ts`, #921): the KEY for a trend's prior-period ghost — a 14-wide dashed ink40 line in the swatch's place. It is not a button: it has no series to raise or hide, so it takes no press, no pressed face and no focus stop, and it never counts as the raised or hidden series.
series int? null The ramp position this chip stands for; absent → its index in the legend.
tone StatusTone? null The chip's series wears a STATUS TONE (`Charts.d.ts` A SERIES MAY WEAR A STATUS TONE, #1425): the SWATCH paints AppChartTheme.toneColor of it (neutral the stepped-back ink20) in place of the ramp position, and the word keeps its ink. The spec's `color` names a token the web kit resolves at paint; a…
value String? null The quiet data-font count beside the word.

ChartLineSeries support

One line series over the shared time domain (`Charts.d.ts` → ChartLineSeries). Points carry their own date, so series need not be equal-length or share a calendar: the platter's x domain is THE CALENDAR — every day from the earliest any series states to the latest (`Charts.d.ts` THE TIME AXIS IS THE…

Field Type Default What it does
label String? null The legend chip's word, and the identity the shared highlight channel raises this series by. Absent → the single-series posture: the series carries no chip and can never be raised or dimmed.
partialDays Map<DateTime, int> const {} The TRAILING PARTIAL buckets among the run, keyed by the bucket's first day, valued by the days of it the window holds (`Charts.d.ts` `ChartPointDatum.partialDays`, THE BUCKET FOLLOWS THE BAND, #786). A point here is a tuple, so the mark rides beside it. The platter reads the UNION over its series,…
points * List<(DateTime, double)> — The series' own (date, value) rows, at most one per calendar day (a second point on a day replaces the first). A day absent here has no reading: the platter's `gaps` decides what the line does across it, and every read there is the em dash.
series int? null The ramp position this series paints at; absent → its index in the list. It is a POSITION, never a color — the ramp belongs to the tier.
tone StatusTone? null A STATUS TONE in place of a ramp position (`Charts.d.ts` A SERIES MAY WEAR A STATUS TONE, #1425): the series' marks, its legend swatch and its tooltip swatch paint the tone (`AppChartTheme.toneColor`; neutral is the stepped-back ink20), and its words never do. Present, series is ignored for paint.…

ChartReference support

THE REFERENCE LINE's datum (`Charts.d.ts` `ChartReference`, #1426): a target or a threshold the HOST states — a value on the platter's own scale and the host's words for it ("Target", "Reorder level"). The platter draws a 1 hairline in `ink60`, dashed 2,3, across the plot at value, and label in the…

Field Type Default What it does
label * String — The host's words, sentence case, one line in the end gutter — ellipsized past the gutter's cap, its full words the label's tooltip.
value * double — On the platter's own scale; finite and at least zero. Anything else is a caller error: debug asserts, release draws no reference.

DonutCenter support

The donut's total-in-the-hole (`Charts.d.ts` → DonutProps `center`). The hole is the one place a proportional chart can state its own total without spending a second element on it.

Field Type Default What it does
label String? null The micro label under it, in the label role. Optional.
value * String — The total, in the compact KPI numeral role.

DonutSegment support

One donut segment (`Charts.d.ts` `DonutSegmentDatum`). label is the byte-exact closed-enum value; tone is the APP's mapping of its vocabulary onto the kit tones (null → the categorical series ramp); glyph is the legend chip's non-color channel (SC 1.4.1).

Field Type Default What it does
count * int — Synced (server-fact) row count for this segment — the filled arc. The segment's total contribution is `count + pending`; the ring closes at 360° over every segment's total.
glyph KitIconRef? null Legend-chip mark — the design-system channel, and the only one. A tone-keyed platter has no ramp position to tell its segments apart by, so the glyph is what carries the reading when color cannot (SC 1.4.1); a hidden chip's treatment is color-only, exactly as the specimen's `.fo-legend__chip--off`…
label * String — The byte-exact segment identity / legend label.
pending int 0 Rows written locally but not yet synced (outbox), in addition to count — rendered as the stroked pending sub-arc. Default 0.
tone StatusTone? null The segment's status tone; null falls through to the categorical ramp.

ScrubDatum support

One scrub-resolvable datum on a continuous / ordered axis (Δ2). The readout the card headline swaps to (D7) reads these fields **verbatim** — no re-derivation — so the scrubbed headline and the plotted datum are the same fact by construction (the collision the goldens missed: the readout must never…

Field Type Default What it does
dateLabel * String — The readout date under the finger (`Jun 5`).
drillInert bool false A synthetic zero-fill day (Δ5): read **honestly** (`0 on Jun 5`) but **drill-inert** — the Δ3 row-count door is suppressed (a `0 rows ›` door is dead). Δ5's "drill-inert" governs the drill, never the read.
rows List<ChartTooltipRow> const [] Every series contributing to this bucket, for the raised tooltip (`Charts.d.ts` → Interaction). A platter fills these; empty is the honest state for a single-series plot that named no series, and tooltipRows is what the card actually renders.
valueText * String — Pre-formatted headline value at this datum (the format engine owns T8/T16 — scrub never re-formats).

StatCellData support

One stat cell in the KPI hero's stat row (Δ8/Δ13).

Field Type Default What it does
label * String — The muted cell label ("Observations").
semanticsLabel * String — The spoken label (the merged node reads "{semanticsLabel}: {value}").
value * int — The backend-owned counter (T11) — count-up animated by StatValue.

Where it lives

Class File
ChartUnit packages/app_widgets/lib/src/kit/charts/chart_unit.dart
ScrubbableChartUnit packages/app_widgets/lib/src/kit/charts/chart_scrub.dart
AppChartTile packages/app_widgets/lib/src/kit/charts/chart_tile.dart
ChartLegend packages/app_widgets/lib/src/kit/charts/chart_legend.dart
ChartRangePresets packages/app_widgets/lib/src/kit/charts/chart_window.dart
TrendLinePlatter packages/app_widgets/lib/src/kit/charts/trend_line.dart
SmallMultiplesPlatter packages/app_widgets/lib/src/kit/charts/small_multiples.dart
CountBarPlatter packages/app_widgets/lib/src/kit/charts/count_bars.dart
CategoricalBarsPlatter packages/app_widgets/lib/src/kit/charts/categorical_bars.dart
DonutPlatter packages/app_widgets/lib/src/kit/charts/donut_platter.dart
AgingStripPlatter packages/app_widgets/lib/src/kit/charts/aging_strip.dart
ChartHero packages/app_widgets/lib/src/kit/charts/chart_hero.dart
RingGauge packages/app_widgets/lib/src/kit/charts/ring_gauge.dart
StatRow packages/app_widgets/lib/src/kit/charts/stat_row.dart
StatValue packages/app_widgets/lib/src/kit/charts/stat_value.dart
KpiValue packages/app_widgets/lib/src/kit/charts/stat_value.dart
SparklineRow packages/app_widgets/lib/src/kit/charts/sparkline_row.dart
ChartDeltaFigure packages/app_widgets/lib/src/kit/charts/chart_delta.dart
DrillChipView packages/app_widgets/lib/src/kit/charts/chart_drill.dart
ChartEmptyGhost packages/app_widgets/lib/src/kit/charts/chart_silhouettes.dart
ChartLoadingSilhouette packages/app_widgets/lib/src/kit/charts/chart_silhouettes.dart
ChartTileErrorSurface packages/app_widgets/lib/src/kit/charts/dashboard_refresh.dart
EarnedStateCountdown packages/app_widgets/lib/src/kit/charts/dashboard_states.dart