verdify_ui 0.1.0 copy "verdify_ui: ^0.1.0" to clipboard
verdify_ui: ^0.1.0 copied to clipboard

Verdify's Flutter design system: 36 token-driven, WCAG 2.2 AA widgets with light and dark themes, built on Material 3.

0.1.0 #

First public release on pub.dev — the complete Verdify Flutter design system: 36 token-driven widgets, light + dark themes (system brightness; ADR-0030), WCAG 2.2 AA, all bound to a generated VerdifyTheme. Bundles the foundation behavior layer (roving focus, anchored overlays, live regions, the two-tone focus ring) and the cross-system a11y token fixes (ADR-0029).

Added #

  • VerdifyTrustScore (non-interactive status output, P3.15): displays the cross-ecosystem trust score — a calm, neutral readout that is never a verdict. Variants: value (score + label + optional scale, default) / meter (score + neutral gauge track) / compact (dense inline). States: default (resolved) · updating (digit transition via AnimatedBuilder + IntTween on motionDurationFast, NOT deliberate — reduced-motion guard jumps to end state instantly) · loading (VerdifySkeleton — infinite animation excluded from goldens) · unavailable (plain text, honest about absence). Non-interactive: no FocusableActionDetector, no focus ring, no target-size floor (spec §6). A11y (spec §7): polite live region via VerdifyLiveRegion (announces updates without interruption); gauge Semantics(value:) as aria-valuenow analog (role=meter); score uses FontFeature.tabularFigures() (spec §2). Token bindings (zero raw colors): score colorTextPrimary / text.h2; label/scale colorTextSecondary / text.label; description/unavailable colorTextMuted / text.caption; gauge track colorControlBg / border colorControlBorder / fill colorControlFg; gauge ends radiusFull. No colorStatus* or colorAction* — the score is neutral (spec §5). 14 component tests + 1 golden (8 scenarios). EXIT=0 on verify.sh (611 tests).

  • VerdifyConsentToggle (compose-a-committed-control, P3.15): explicit, revocable affordance for granting / withdrawing a single data-use permission. Composes VerdifySwitch via its public label / description / value / onChanged / loading seam. Layout: scope as Switch label (primary accessible name), recipient + detail as Switch description (secondary AT text) — spec §7 accessible name binds scope AND recipient. Variants: grant (default) / withEvidence (adds evidence affordance link) / agentScoped (recipient is an AI agent). States: default (not granted, off) · granted (on) · hover/pressed/focus/disabled/loading (all delegated to VerdifySwitch). brand ≠ state: granted track uses colorActionPrimaryBg* (action color), NOT colorStatusVerified* (verified green) — granting permission is a brand moment, not a verification result (spec §5/§8). Token bindings (zero raw colors): evidence link colorActionGhostFg; all Switch track/thumb/focus tokens come from VerdifySwitch. 18 component tests + 1 golden (8 scenarios).

  • VerdifyIdentityChip (compose-a-committed-control, P3.15): compact inline reference to a single identity — person or AI agent. Composes committed widgets via public seams only: VerdifyAvatar (decorative, decorative:true), VerdifyAgentBadge (when isAgent:true), VerdifyVerifiedBadge (when showVerified:true), VerdifyButton (ghost, remove control on removable variant). Variants: static_ (read-only, non-interactive default), interactive (the chip is a button with keyboard + focus ring + 44px tap-target), removable (trailing remove control named "Remove

  • VerdifyDataGrid (Phase-3 assembly, the final complex widget): the interactive, roving sibling of VerdifyTable. No brand-book spec exists (decision D-FW5) — ported from the COMMITTED REACT CODE (packages/ui/src/components/data-grid); gated on React-code PARITY + a11y, not a spec. API: a columns + rows DATA MODEL (VerdifyDataGrid(columns:, rows:, …), the VerdifyTable Flutter idiom) rather than React's compositional slots — VerdifyDataGridColumn (key/header/sortable/onSort/mono/flex), VerdifyDataGridRow (id + absolute rowIndex + cells map), VerdifyDataGridCell (text/child + status), VerdifyDataGridBulkAction (label/onPressed/destructive). 2D cell roving (the defining behavior, ported from the React (row,col) roving model): the grid is a SINGLE tab stop (FocusTraversalGroup + per-cell ExcludeFocusTraversal roving tabindex — only the active cell is Tab-traversable); a (row,col) cursor over a Map<String,FocusNode> keyed by cell position; a grid-level ShortcutsActions binds ArrowRight/Left (inline axis, clamp = no wrap at the edge), ArrowUp/Down (between rendered rows), Home/End (row ends), Ctrl/Cmd+Home/End (grid corners), PageUp/PageDown (a 10-row viewport jump, clamped), and Ctrl/Cmd+A (onSelectAll); each move requestFocuses the destination node and draws the visible 2px focus ring on the active cell; focusing a cell (pointer/Tab/arrow) re-seeds the cursor so click + Tab + arrows agree. Selection (React multiple): leading box-only checkboxes (the VerdifyTable._TableCheckbox precedent — composes VerdifyCheckbox's token model: brand action-primary fill on checked, never status-verified) + a parent select-all (indeterminate when partial) + a bulk-action bar (composed of VerdifyButtons — primary Export, destructive Revoke) that appears only when ≥1 row is selected. Sort: a sortable header (VerdifyButton-less interactive control with focus ring + tap floor) whose accessible name carries the direction (Sort Status, ascending — the aria-sort analog) + a non-color caret (Icons.arrow_upward/downward — shape, never color alone); onSort(requestedDirection) mirrors VerdifyTable. States: default · hover (colorActionGhostBgHover row fill) · selected · sorted · focus (active-cell ring) · loading (a quiet spinner slot, grid name carries , loading) · empty (a plain colorTextMuted line, NOT an error) · error (colorStatusCriticalFg honest copy). Status cell: the status fg paired with the cell's TEXT (colorStatus{Verified,Signal,Caution,Critical}OnSurface) — readable in grayscale, lives in the cell only (never the row/header/selection accent), never the brand (G-U2). Virtualization: body rows render through a lazy ListView.builder (rows build on demand, not all eagerly); the full-set shape (rowCount/colCount + each row's absolute rowIndex) rides on the grid Semantics name so "12,000 rows" survives a windowed tree. Full windowing/recycling beyond ListView.builder's laziness, and per-cell aria-rowindex/colindex + a native role=grid, are DEFERRED (documented partial — the same Flutter-has-no-grid-role limitation VerdifyTable carries; the React cell-entry/exit controls=single|multiple delegation is also not ported — the data-model cells host values/status, not arbitrary nested controls). A11y (spec-less; React-parity + guidelines): grid name carries label + full-set shape + state; meetsGuideline(iOSTapTargetGuideline) (checkboxes get the 44px column via padH:0 so the cell padding never clamps the box's hit area; the sort header gets a ConstrainedBox(minHeight: targetMobile)); meetsGuideline(labeledTapTargetGuideline) (every control named); meetsGuideline(textContrastGuideline) AA over cell text. DEVIATION (a real finding the Flutter contrast gate caught that React's jsdom tests cannot): the React selected-row class is bg-action-secondary-bg-hover (#D6DCE8), but a status cell's on-surface text on that fill is 3.99:1 (sub-AA); we bind the RESTING colorActionSecondaryBg (#E8ECF4, ≥4.6:1, still the neutral action-secondary selection accent) and add a leading colorActionSecondaryBorder accent BAR so a selected row stays distinct from a hovered one (selection is never the fill alone). A token-tier selection-bg guaranteed against status text would restore the darker fill (deferred, no version bumps this wave). Token bindings (zero raw colors, gate-enforced): canvas colorSurfaceCanvas/colorSurfaceBorder/radiusMd; header colorSurfaceRaised+shadowSm+colorBorderDefault rule, label colorTextSecondary@v.text.label; cell text colorTextPrimary@v.text.body, mono v.text.mono (LTR-isolated, G-U6); row hover colorActionGhostBgHover, selected colorActionSecondaryBg+colorActionSecondaryBorder bar; active-cell ring colorBorderFocus/focusRingWidth; status colorStatus*OnSurface; empty colorTextMuted; error colorStatusCriticalFg; bulk bar colorSurfaceRaised+colorBorderDefault, actions via VerdifyButton; sort caret/label colorActionGhostFg, sort hover colorActionGhostBgHover; row transition motionDurationFast+motionEasingVerdify, instant under reduced motion. 21 component tests (renders columns+rows, grid name carries full-set shape, single tab stop, ArrowRight/Left no-wrap, ArrowUp/Down, Home/End, sort fires onSort + names direction, row-checkbox toggle, select-all, bulk bar appears/hides, bulk action fires, empty/loading/error slots, status-as-text, iOS tap-target, labeled controls, AA contrast, read-only has no selection chrome) + 1 golden (5 scenarios: comfortable/sorted, compact, selected+bulk-bar, empty, error). Gallery _DataGridGallery added. Barrel export: src/components/data_grid/verdify_data_grid.dart.

  • VerdifyCommandPalette (Phase-3 assembly): a modal keyboard-first launcher opened via showVerdifyCommandPalette(context, inputLabel:, items:, …) — type a few letters, run a command or jump to a place without leaving the keyboard (spec §1). Modal shell (spec §2/§7): showGeneralDialog (the showDialog analog that lets the panel drive its own fade) → ModalBarrier + BlockSemantics (page behind removed from a11y tree — aria-modal analog), focus TRAPPED in the route's FocusScope, returns focus to the opener on close (the route restores it), Esc + scrim-tap dismiss; Colors.black54 scrim (named constant — no --color-scrim-* in the Flutter emit, deviation documented, same as Sheet/Dialog). The active-descendant model (spec §6/§7/§8 — the KEY behavior): the INPUT keeps DOM focus the whole time; the input's own Focus(onKeyEvent:) handles Down/Up/Home/End to move a tracked _activeId over the VISIBLE RUNNABLE rows (group labels + disabled rows skipped), wrapping at the ends; the active row is highlighted (secondary hover fill) and exposed via Semantics(selected: true) (the aria-activedescendant + aria-selected analog — Flutter has no aria-activedescendant; modeled by the input keeping focus + the active row's selected: Semantics, documented limitation); Enter activates the active row (runs onRun + closes via Navigator.pop); focus NEVER leaves the input (proven by a test: after each arrow key the input still has focus) — the OPPOSITE of VerdifySelect/VerdifyMenu (which MOVE focus into the listbox), and §8 names focus-moving as the forbidden anti-pattern. Input (combobox, spec §2): a TextField (Material defaults bound away — cursor colorBorderFocus, no underline border, content padding lifts the field to the 44px tap floor) focused on open (post-frame requestFocus); typing FILTERS the results live (label OR secondary contains the query, case-insensitive — the palette's type-ahead, NOT the foundation type_ahead keystroke-jump); leading search glyph (decorative ExcludeSemantics) + an optional scope prefix chip (spec §3 scoped). Results (spec §2): grouped rows (consecutive same-group rows under one muted header group-label) + optional recent list (shown when query empty, under a muted "Recent" heading) + an empty state (centered colorTextSecondary text — announced as text, never silence) + optional footer (key hints). Variants (spec §3): commands (default) / navigation / withRecent / scoped (scope chip). States (spec §4): default (open, input focused, first runnable row active) · hover (pointer hover makes the row active — kept in sync with keyboard so Enter runs what is highlighted) · pressed (tap runs) · disabled row (colorTextDisabled, skipped by arrow nav, not activatable). A11y (spec §7): input is Semantics(textField:, label: inputLabel) (combobox, names itself — placeholder is never the name); listbox is Semantics(container:, label:); each row is MergeSemantics + Semantics(selected:, enabled:, label:) (option); group labels are header nodes skipped by nav; meetsGuideline(iOSTapTargetGuideline) over input + rows; textContrastGuideline over the readable content (input + row labels = colorTextPrimary on colorSurfaceRaised, AA). Token bindings (spec §5, zero raw colors): panel colorSurfaceRaised + colorSurfaceBorder + radiusLg + shadowLg; input colorControlFg (query) / colorControlPlaceholder (placeholder + search glyph) / colorBorderFocus (cursor); input-to-results + group + footer dividers colorBorderDefault; active-row fill colorActionSecondaryBgHover (spec §5 — NEVER a status/brand fill: the active row is location, not a verified result, brand != state §3/§8); row label colorTextPrimary at v.text.body; row secondary colorTextSecondary at v.text.label; group label + recent heading + shortcut hint colorTextMuted at v.text.label; disabled row colorTextDisabled (DEC-C); empty + footer text colorTextSecondary; scope chip colorSurfaceInput/colorSurfaceBorderMuted/radiusSm; row corners radiusMd; tap floor targetMobile; DEC-A: the input value pins 16px (iOS no-zoom floor), the body role rides via leading/tracking. Open fade motionDurationBase (spec §5), collapsed to instant under MediaQuery.disableAnimations (reduced-motion guard) — never the deliberate check duration. 14 component tests (open-input-focused, return-focus-to-opener, live filter, empty state, recent→results, Down/Up/Home/End move active while focus STAYS on input, arrow nav skips group labels + disabled, Enter activates + closes, tap activates + closes, textField combobox, active-row selected: tracks, iOS tap-target, AA contrast) + 1 golden (4 scenarios: commands+footer, scoped+chip, with-recent, empty). Gallery section _CommandPaletteGallery added to example/lib/main.dart. Barrel exports the public API (VerdifyCommandPaletteItem, VerdifyCommandPaletteOptionRow, VerdifyCommandPalettePanel, VerdifyCommandPaletteVariant, showVerdifyCommandPalette) via a show clause (private helpers stay internal). Concern (not re-decided here): group-label/shortcut-hint colorTextMuted on colorSurfaceRaised is 2.55:1 — below WCAG AA 4.5:1 for normal text; the spec §5 mandates the muted token, and the same binding/contrast holds in VerdifySelect/VerdifyMenu group labels (whose contrast tests, like this one, run over the closed/flat surface). This is a token-tier concern shared across the listbox family, surfaced for a holistic decision rather than unilaterally overridden in one component.

  • VerdifyAccordion (Phase-3 assembly): stacks collapsible sections (spec §1). Anatomy (spec §2): root container + item list, each with a header (Semantics header: true — h2 analog) wrapping a trigger button (focusable control) + indicator chevron (animated rotate, decorative ExcludeSemantics) + panel (collapsible region, role=region labeled by trigger). Variants (spec §3): single (default — only one open at a time) / multiple (any number open) / collapsible (single allows all-closed) / disabled per item. States (spec §4): default (collapsed) · hover (colorActionSecondaryBgHover) · pressed · focus (2 px colorBorderFocus ring) · open (panel revealed + chevron rotated 180°) · disabled (colorTextDisabled, skipped by arrow). Height animation: AnimatedSize at motionDurationBase + motionEasingVerdify; reduced-motion guard collapses to Duration.zero (VerifiedBadge seed pattern). Roving model (spec §6): VerdifyRovingGroup (vertical, manual activation) — Down/Up move focus, Home/End jump, wrap, skip-disabled, Enter + Space toggle focused item. A11y (spec §7): Semantics(header: true, button: true, enabled:, expanded:, label:) on each trigger via MergeSemantics; panel Semantics(container: true, label: title) (aria-labelledby analog; Flutter programmatic ID link documented as limitation); indicator ExcludeSemantics. Token bindings (spec §5, zero raw colors): colorControlBg trigger rest · colorControlFg trigger text · colorActionSecondaryBgHover hover/pressed · colorActionGhostFg chevron · colorBorderDefault item dividers · colorBorderFocus/focusRingWidth focus ring · colorSurfaceCanvas panel bg · colorTextPrimary panel text · colorTextDisabled disabled title + icon · targetMobile tap floor · radiusMd corners · motionDurationBase + motionEasingVerdify. 25 component tests + 1 golden (4 scenarios). Gallery _AccordionGallery added. Barrel export: src/components/accordion/verdify_accordion.dart.

  • VerdifySidebar (Phase-3 assembly): primary navigation rail (spec §1). Anatomy (spec §2): nav landmark + optional header + optional group (labeled cluster) + item list (icon + label + optional badge) + current indicator + optional collapse-toggle + optional footer. Variants (spec §3): expanded (default, icon + label) / collapsed (icon only, label in Tooltip); overlay deferred — callers use VerdifySheet for the modal-drawer case (documented partial). States (spec §4): default · hover (colorActionGhostBgHover) · pressed · focus (2 px colorBorderFocus ring via BoxShadow) · current (indicator bar + colorActionPrimaryBg/Fg + Semantics(selected:true)aria-current=page analog) · disabled (colorTextDisabled, skipped by arrow). Roving model (spec §6): VerdifyRovingGroup (vertical axis) — Down/Up move, Home/End jump, wrap, skip-disabled, Enter + Space activate. Collapse toggle: Semantics(button:, expanded:, label: 'Collapse sidebar'/'Expand sidebar'). Token bindings (spec §5, zero raw colors): colorSurfaceRaised rail · colorSurfaceBorder edge · colorSurfaceBorderMuted dividers · colorTextSecondary rest label · colorTextPrimary current label · colorTextMuted group heading · colorTextDisabled disabled · colorActionPrimaryBg current indicator + fill · colorActionPrimaryFg current icon · colorActionGhostFg resting icon + toggle glyph · colorActionGhostBgHover hover fill · colorBorderFocus/focusRingWidth focus ring · shadowSm elevation · radiusMd item corners · targetMobile tap floor. 24 component tests + 1 golden (6 scenarios). Gallery _SidebarGallery added. Barrel export: src/components/sidebar/verdify_sidebar.dart.

  • VerdifySheet (Phase-3 assembly): modal panel that slides in from a viewport edge to hold a focused secondary task (spec §1). MODAL — focus is trapped inside while open; returns to trigger on close. Uses showGeneralDialog (the showDialog analog for custom animations): ModalBarrier with Colors.black54 scrim (named constant; --color-scrim-dark not in token emit — deviation documented), BlockSemantics removes page from a11y tree, route's FocusScope traps Tab/Shift+Tab. Anatomy (spec §2): scrim + panel + header (title + REQUIRED close control) + body (scrollable) + optional footer (pinned). Variants (spec §3): side = inlineEnd (default, right)/inlineStart (left)/blockEnd (bottom); size = sm (360px)/md (480px, default)/lg (640px). States (spec §4): opening = SlideTransition + FadeTransition driven by internal AnimationController reading v.motionDurationBase + v.motionEasingVerdify; reduced-motion guard collapses both to instant (MediaQuery.maybeDisableAnimationsOf). Close control (ghost icon, colorActionGhostFg/BgHover) ALWAYS present. Esc + scrim tap + close control all dismiss. A11y (spec §7): Semantics(scopesRoute: true, namesRoute: true, label: title) on the panel (role=dialog + aria-labelledby analog); close labeled "Close" with isButton; focus trap from route's FocusScope; return-focus from ModalRoute. Header/footer pinned; only body scrolls (Expanded + SingleChildScrollView). Token bindings (spec §5, zero raw color constructors): colorSurfaceRaised fill, colorSurfaceBorder panel edge, colorSurfaceBorderMuted dividers, colorTextPrimary title (v.text.h2), colorTextSecondary body, colorActionGhostFg/BgHover close, shadowLg elevation, radiusLg leading corners, motionDurationBase + motionEasingVerdify. 13 component tests + 1 golden (4 scenarios). Gallery section _SheetGallery added. Barrel export: src/components/sheet/verdify_sheet.dart.

  • VerdifyTooltip (Phase-3 assembly): small floating label describing the control it is attached to (spec §1). NON-MODAL — focus never enters the tooltip; stays on the trigger the whole time. Variants (spec §3): label (tooltip text IS the accessible name for icon-only triggers) / description (supplementary hint on a control that already has a visible label). States (spec §4): hidden by default; hover shows after a 500 ms delay; focus shows immediately, no delay; Escape dismisses without moving focus (WCAG 1.4.13 dismissible); hoverable — moving the pointer onto the tooltip keeps it open (WCAG 1.4.13 hoverable); persistent — stays until hover/focus leaves. Uses VerdifyAnchoredOverlay (NON-modal, no route, no focus trap) + VerdifyFadeIn (fast motion, instant under reduced-motion). A11y (spec §7): trigger is the only tab stop; tooltip content is in the a11y tree while shown; Semantics(tooltip: content) on the panel; ExcludeFocus removes panel from traversal; focus never moves to tooltip; Escape handled on the Focus wrapper around the trigger. Token bindings (spec §5, zero raw colors): colorSurfaceRaised fill, colorSurfaceBorder border, colorTextPrimary text, radiusSm corner, shadowMd elevation, motionDurationFast + motionEasingVerdify fade. 12 component tests + 1 golden (3 scenarios). Gallery section _TooltipGallery added. Barrel export: src/components/tooltip/verdify_tooltip.dart.

  • VerdifyTextarea (Phase-3 assembly): multi-line text field for free text (notes, reasons, descriptions) when the answer runs to more than one line (spec §1). Anatomy (spec §2): label (required, accessible name via InputDecoration.labelText) + multi-line field + optional placeholder (hint, never the label) + optional description + optional character counter + optional error. Variants (spec §3): resize vertical(default)/none/autoGrow (autoGrow: maxLines: null so the field grows with content); counter (showCounter: true with maxLength). States (spec §4): default · hover (border darkens) · focus (2 px colorBorderFocus ring) · disabled (enabled: false — removed from tab order) · error (errorTextcolorStatusCriticalBorder + colorStatusCriticalFg) · read-only (readOnly: true — value stays selectable, stays tabbable). Keyboard (spec §6): Enter inserts a newline; Tab moves focus OUT — not trapped, never inserts a tab character; all platform text shortcuts pass through. A11y (spec §7): accessible name from labelText (never hintText — the placeholder analog); error via InputDecoration.errorText so Flutter includes it in the semantics tree (aria-invalid + describedby analog); counter is a plain Text below the field (aria-live="polite" analog); enabled: false removes from tab order; readOnly: true keeps field tabbable. Material defaults overridden: cursor colorBorderFocus, style colorControlFg, fill colorControlBg, all four border states (enabled/focused/error/focusedError/disabled). Counter uses own token-bound Text (Material's counterText leaks ColorScheme). Token bindings (spec §5, zero raw colors): control-bg colorControlBg; resting border colorControlBorder; hover border colorBorderStrong; focused border colorBorderFocus; error border colorStatusCriticalBorder; error text colorStatusCriticalFg; text colorControlFg; placeholder colorControlPlaceholder; label/description colorTextSecondary; counter colorTextMuted; disabled colorTextDisabled; focus ring colorBorderFocus/focusRingWidth; cursor colorBorderFocus; radius radiusMd; DEC-A text role v.text.body; tap-floor targetMobile. 21 component tests + 1 golden (11 scenarios). Gallery section _TextareaGallery added to example/lib/main.dart. Barrel export: src/components/textarea/verdify_textarea.dart.

  • VerdifySwitch (Phase-3 assembly): toggles a single setting on/off with immediate effect; no Save step (spec §1). Anatomy (spec §2): track (rounded, changes color on/off) + thumb (slides) + label (required, accessible name) + optional description. Variants (spec §3): size md/sm; labelPlacement before(default)/after. States (spec §4): off/on (track color changes), hover, pressed, focus (2 px ring via BoxShadow(colorBorderFocus, spreadRadius: focusRingWidth)), disabled (DEC-C: label/thumb via colorTextDisabled, never blanket opacity), loading (spinner in thumb, blocks toggling; Flutter limitation: no direct aria-busy — document at consumption). Keyboard (spec §6): Tab/Shift+Tab navigate; Space and Enter toggle via ShortcutsActionsActivateIntent (mandatory — copy of Checkbox pattern). A11y (spec §7): Semantics(toggled:, hasToggledState:, enabled:, label:) → role=switch + checked-state; MergeSemantics for one a11y node; tap-target floor v.targetMobile (44 px). Thumb slide + track color animated with motionDurationFast + motionEasingVerdify; reduced-motion guard (VerifiedBadge seed pattern). Token bindings (spec §5, zero raw colors): track-off colorControlBg/colorControlBorder; track-on colorActionPrimaryBg/BgHover/BgActive/Border; thumb colorControlFg; label colorTextPrimary; description colorTextSecondary; disabled colorTextDisabled; focus colorBorderFocus/focusRingWidth; tap-floor targetMobile; radius radiusFull; motion motionDurationFast/motionEasingVerdify. 17 component tests + 1 golden (11 scenarios). Gallery section _SwitchGallery added to example/lib/main.dart. Barrel export: src/components/switch/verdify_switch.dart.

  • VerdifyTable (semantic data table, Phase-3 assembly): presents structured data in rows and columns for reading, comparing, and sorting (spec §1). API: columns: List<VerdifyTableColumn> (key, header, sortable, onSort, flex) + rows: List<VerdifyTableRow> (id, cells map, selected); caption, density (comfortable/compact), rule (horizontal/grid/zebra), selectable/selectedIds/onSelectionChanged, sortedColumnKey/sortAscending, loading, emptyMessage, errorMessage. Core shipped (spec §3): density comfortable/compact, rule horizontal/grid/zebra, sortable headers (per-column toggle ascending/descending via onSort), selectable rows (leading compact checkbox + select-all in header). Deferred / documented partials: sticky-header (Flutter Table layout primitive requires custom overlay for fixed-header scroll — deferred post-P3; DataGrid is the right home for this complexity); row-actions trailing cell (API stable for the add); tfoot (callers may append a footer widget below). Selection cells use an internal _TableCheckbox (box-only, no visible text label) to avoid overflow in the fixed 48px selection column — the accessible name ('Select all rows'/'Select row N') is carried by the Semantics label. A11y (spec §7, Flutter limitation): Flutter's Table is a layout primitive with no native HTML <table>/<th scope> semantics; column headers wrapped in Semantics(header: true); caption accessible as visible text; sortable header button labeled with direction ("Sort Name, ascending"); _TableCheckbox uses MergeSemantics(Semantics(checked:, mixed:, label:)); status cells use text + icon, never color alone. Sort direction: caret icon (Icons.arrow_upward/downward) + accessible label, never color-only. Empty: colorTextSecondary message. Error: colorStatusCriticalFg message paired with text. Loading: 3 VerdifySkeleton.text rows. Row hover: colorSurfaceRaised fill (no motion in table rows — no AnimatedContainer, plain BoxDecoration). Token bindings (spec §5, zero raw colors): canvas colorSurfaceCanvas; raised colorSurfaceRaised; header label colorTextSecondary at v.text.label; cell text colorTextPrimary at v.text.body; rules colorBorderDefault; sortable label/icon colorActionGhostFg; sortable hover colorActionGhostBgHover; focus ring colorBorderFocus/focusRingWidth; tap floor targetMobile; error colorStatusCriticalFg. 16 component tests + 1 golden (8 scenarios, reduced-motion host). Gallery section added. Barrel export: src/components/table/verdify_table.dart.

  • VerdifyAvatar (identity image placeholder, Phase-3 assembly): a small non-interactive image that stands in for an identity in lists, headers, and threads (spec §1). Anatomy (spec §2): container clips image or fallback (initials or generic glyph); adornment slot is external (not part of Avatar). Variants (spec §3): shape circle (default) / rounded; size sm/md(default)/lg; showBorder hairline colorSurfaceBorderMuted. States (spec §4): default (image) / loading (VerdifySkeleton.circle or .block, reduced-motion guard via MediaQueryData(disableAnimations:true) host in goldens) / fallback (initials from _initials() — up to two chars — or Icons.person when name is empty). Non-interactive: no focus, no tap, no hover/disabled (spec §6). A11y (spec §7): informative → Semantics(image: true, label: displayName) (the initials inside are ExcludeSemantics — AT hears the name, never the letters); decorative: trueExcludeSemantics; loading → skeleton (already ExcludeSemantics inside VerdifySkeleton). Image loaded via Flutter Image(imageProvider, loadingBuilder, errorBuilder) — error triggers setState(() => _imageError = true) → fallback. Token bindings (spec §5, zero raw colors): fallback fill colorSurfaceRaised; initials/glyph colorTextSecondary; border colorSurfaceBorderMuted; radius radiusFull (circle) / radiusMd (rounded). 15 component tests + 1 golden (9 scenarios, reduced-motion host). Gallery rows added. Barrel export: src/components/avatar/verdify_avatar.dart.

  • VerdifyPagination (nav landmark + page controls, Phase-3 assembly): splits a long result set into numbered pages with Prev, page-number run, and Next (spec §1). Variants (spec §3): numbered (default — windowed page buttons ±2 around current + ellipsis for long ranges), prevNext ("Page m of n" readout between Prev/Next for compact surfaces); sizes sm/md/lg. States (spec §4): Prev/Next disabled at edges (non-operable, colorTextDisabled, Semantics(enabled: false)); current page non-interactive accent fill (colorActionPrimaryBg/colorActionPrimaryFg — brand ≠ state: this is a position marker, never the verified green); enabled controls hover/pressed/focus (2 px ring). Keyboard (spec §6): Tab visits each enabled control; Enter/Space activate via ShortcutsActionsActivateIntent. A11y (spec §7): Semantics(container: true, label: 'Pagination', explicitChildNodes: true) landmark; Prev/Next Semantics(button:, enabled:, label: 'Previous page'/'Next page'); page buttons Semantics(button:, label: 'Go to page N'); current page labeled 'Page N, current' (aria-current analog); ellipsis ExcludeSemantics. Token bindings (spec §5, zero raw colors): rest colorTextSecondary; hover colorActionGhostBgHover; Prev/Next icon colorActionGhostFg; current fill colorActionPrimaryBg, label colorActionPrimaryFg; disabled colorTextDisabled; focus colorBorderFocus/focusRingWidth; tap floor targetMobile; corner radiusMd. 17 component tests + 1 golden (6 scenarios). Gallery rows added. Barrel export: src/components/pagination/verdify_pagination.dart.

  • VerdifyBreadcrumb (nav landmark + trail, Phase-3 assembly): navigation landmark showing the current-page hierarchy and letting users step back up it (spec §1). Anatomy (spec §2): Semantics(container: true, label: 'Breadcrumb', explicitChildNodes: true) landmark → Row of VerdifyBreadcrumbItem link items interleaved with _Separator glyphs (each ExcludeSemantics / aria-hidden) → _CurrentItem plain text. Variants (spec §3): default (full trail), with-root-icon (supply icon on root item), collapsed (collapsed: true — middle items behind ellipsis; full overflow-Menu deferred, documented). States (spec §4): link items support hover (colorActionGhostBgHover fill + colorTextPrimary label) / pressed / focus (2 px colorBorderFocus ring) / disabled (colorTextDisabled, excluded from Tab order via ExcludeFocusTraversal). Current page is plain non-interactive text. Keyboard (spec §6): Tab visits each enabled link item in trail order; Enter and Space activate via ShortcutsActionsActivateIntent; disabled items skipped. A11y (spec §7): landmark labeled 'Breadcrumb'; link items are Semantics(button: true, enabled:); current page has no button role; separators ExcludeSemantics. Token bindings (spec §5, zero raw colors): link rest colorTextSecondary; hover fill colorActionGhostBgHover; hover/active text colorTextPrimary; current colorTextPrimary; separator colorTextMuted; disabled colorTextDisabled; focus ring colorBorderFocus/focusRingWidth; tap floor targetMobile; radius radiusSm. 14 component tests + 1 golden (5 scenarios). Gallery rows added. Barrel export: src/components/breadcrumb/verdify_breadcrumb.dart.

  • VerdifyAgentBadge (AI-agent actor marker, Phase-3 assembly): non-interactive StatelessWidget that marks an actor as an AI agent (spec §1). Mirrors VerdifyBadge structure (same non-interactive template). Variants (spec §3): neutral (default, neutral surface palette — never action or status tier), caution (agent needs attention), critical (agent failed/revoked). Status color is ALWAYS paired with label text naming the state — human/agent distinction NEVER rests on color alone (WCAG 1.4.1, spec §8). Never-nameless assert: icon-only markers require semanticLabel. A11y (spec §7): visible label is accessible name (no role); icon ExcludeSemantics (aria-hidden); icon-only → Semantics(image: true, label: semanticLabel). No focus, no focus ring, no tap-target floor (not interactive). Token bindings (spec §5, zero raw colors, zero action/verified-status tokens — brand != state): neutral colorSurfaceRaised/colorSurfaceBorderMuted/colorTextSecondary; caution colorStatusCaution{Bg,Border,Fg}; critical colorStatusCritical{Bg,Border,Fg}; radiusFull pill; v.text.caption. 18 component tests + 1 golden (8 scenarios). Gallery rows added to example/lib/main.dart. Barrel export: src/components/agent_badge/verdify_agent_badge.dart.

  • VerdifySeparator (structural divider, Phase-3 assembly): non-interactive StatelessWidget that draws a 1px rule dividing content groups (spec §1). Variants (spec §3): orientation horizontal (default) / vertical; decorative (removes from a11y tree via ExcludeSemantics); with-label (centered text breaks rule into two segments). A labeled separator is ALWAYS semantic — decorative is ignored when label is set (spec §3). A11y (spec §7): decorative → ExcludeSemantics (role="none" analog); semantic → Semantics() container; labeled → Semantics(label: label, excludeSemantics: true) so the label is the accessible name (mirrors React's aria-label on the separator div). Flutter limitation documented: Flutter stable has no SemanticsRole.separator — the closest analog is a plain Semantics() container. No focus, no focus ring, never a tab stop. Token bindings (spec §5, zero raw colors): rule colorBorderDefault; label text colorTextSecondary at v.text.caption. Layout spacing is plain EdgeInsets constants (not tokens — spacing is not emitted to Flutter). 15 component tests + 1 golden (5 scenarios: horizontal semantic, horizontal decorative, labeled, vertical, semantic-between-sections). Gallery rows added to example/lib/main.dart. Barrel export: src/components/separator/verdify_separator.dart.

  • VerdifyLabel (form-control label, Phase-3 assembly): non-interactive StatelessWidget that names its associated control (spec §1). Three variants (spec §3): default (label text in colorTextPrimary), required (decorative * glyph in ExcludeSemantics paired with a visually-hidden "required" word so meaning is NEVER color alone — WCAG 1.4.1), optional (colorTextSecondary hint). Disabled state: label renders in colorTextDisabled to reflect the control's state (DEC-C). A11y (spec §7): no focus, no focus ring, never a tab stop; visible label text is the accessible name in traversal order; Flutter limitation documented — no native <label for> association; callers must pass the same label text to the control's own Semantics(label:) or VerdifyTextField(label:). Token bindings (spec §5, zero raw colors): label text colorTextPrimary / disabled colorTextDisabled; optional hint colorTextSecondary at v.text.caption; required mark colorStatusCriticalFg; label type role v.text.label. 13 component tests + 1 golden (5 scenarios: default, required, optional, disabled, required+disabled). Gallery rows added to example/lib/main.dart. Barrel export: src/components/label/verdify_label.dart.

  • VerdifySkeleton (loading placeholder, Phase-3 assembly): non-interactive StatefulWidget with named constructors for each variant: VerdifySkeleton.text(width, height), .block(width, height), .circle(diameter). VerdifySkeletonGroup composes shapes into a silhouette (card, list row) with _kGap (8 px) between children. Shimmer: a CustomPainter sweeping a colorSurfaceRaised band across the colorSurfaceInput base, driven by motionDurationAmbient + motionEasingAmbient. Reduced-motion guard (VerifiedBadge seed pattern): controller never started when MediaQuery.disableAnimations is true — static neutral shape, no pending frames. A11y (spec §7): ExcludeSemantics wraps every shape — the skeleton is entirely aria-hidden; the owner announces loading via aria-busy/live-region (Flutter limitation documented: aria-busy has no direct Flutter API; callers should wrap the skeleton's container in VerdifyLiveRegion and announce via announceToAssistiveTechnology). Token bindings (spec §5, neutral surface only — zero action/status colors, zero raw color literals): base colorSurfaceInput; shimmer colorSurfaceRaised; block/group hairline border colorSurfaceBorderMuted; text corner radiusSm; block corner radiusSm (≤40 px height) or radiusMd (>40 px); circle radiusFull; shimmer motionDurationAmbient + motionEasingAmbient. 12 component tests + 1 golden (6 scenarios: text full/short, block, circle, group silhouette, reduced-motion static). Gallery rows added to example/lib/main.dart. Barrel export: src/components/skeleton/verdify_skeleton.dart.

  • VerdifyProgress (task-advancement bar, Phase-3 assembly): non-interactive StatefulWidget with determinate (fill = value 0–1) and indeterminate (moving indicator) variants. [label] required (Progress is never unlabeled). Optional [valueText] (determinate-only), [description], and [error] (critical border + message text — never color alone). Reduced-motion guard (VerifiedBadge seed pattern): indeterminate controller never starts when MediaQuery.disableAnimations is true; determinate uses Duration.zero fill transition. A11y (spec §7): VerdifyLiveRegion container (polite role="status" analog); Semantics(label:, value:) exposes the task name + progress value to AT (determinate only; indeterminate omits value per spec — no aria-valuenow); error text in semantics tree (Flutter aria-invalid limitation documented). Token bindings (spec §5, zero raw colors): track colorControlBg / colorControlBorder; fill/indicator colorActionPrimaryBg; error border colorStatusCriticalBorder; error text colorStatusCriticalFg; label colorTextPrimary; value-text + description colorTextSecondary; radius radiusFull; determinate fill motion motionDurationFast + motionEasingVerdify; indeterminate motionDurationAmbient. Implementation note: FractionallySizedBox/AnimatedFractionallySizedBox used instead of LayoutBuilder (the latter doesn't support intrinsic dimensions — crashes in Alchemist golden host). 14 component tests + 1 golden (6 scenarios). Gallery rows added to example/lib/main.dart. Barrel export: src/components/progress/verdify_progress.dart.

  • VerdifySpinner (unmeasured-wait indicator, Phase-3 assembly): non-interactive StatefulWidget with a continuously-rotating arc on motionDurationAmbient + motionEasingAmbient. Sizes sm/md/lg (16/20/32 px indicator). Reduced-motion guard (VerifiedBadge seed pattern): when MediaQuery.disableAnimations is true the AnimationController is never started — the arc renders as a static glyph with no pending frames. A11y (spec §7): indicator is ExcludeSemantics (decorative); container is VerdifyLiveRegion (Semantics(liveRegion: true) — the role="status" analog); accessible name from visible label or accessibleLabel; Flutter limitation: no direct aria-busy API — state is carried by the live-region label. Token bindings (spec §5, zero raw colors): arc colorActionPrimaryBorder (Sovereign Violet on surface — NOT colorActionPrimaryFg which is near-white text-on-violet); track colorBorderDefault; label colorTextPrimary at v.text.caption; motion motionDurationAmbient + motionEasingAmbient. Gallery row added to example/lib/main.dart. Barrel export: src/components/spinner/verdify_spinner.dart. 11 component tests + 1 golden (6 scenarios: sm/md/lg no-label, md/lg with-label, reduced-motion static).

  • foundation/live_region.dart (live-region foundation, Phase-3 promotion): promoted from VerdifyToastRegion's inline SemanticsService.sendAnnouncement at the second consumer (VerdifyAlert), per the build-on-brand-flutter extract-at-second-use rule. Exposes two primitives: announceToAssistiveTechnology(context, message, {assertive}) — wraps SemanticsService.sendAnnouncement(View.of(context), …, assertiveness: assertive ? Assertiveness.assertive : Assertiveness.polite) — and VerdifyLiveRegion — a pure-semantics StatelessWidget wrapper adding Semantics(liveRegion: true) to its subtree. Toast refactored to consume announceToAssistiveTechnology (8/8 Toast tests stay green). 2 focused foundation unit tests (VerdifyLiveRegion renders child + sets SemanticsFlag.isLiveRegion). Not exported from the public barrel (internal foundation, imported by consumers via src/foundation/). Zero raw color literals.

  • VerdifyAlert (inline status message, Phase-3 assembly): an in-page message that holds its layout position until the condition changes or the reader dismisses it (spec §1). Anatomy (spec §2): root bordered container → icon (required, fixed per variant, decorative ExcludeSemantics) → optional titlebody (required) → optional actions (≤ 2 VerdifyButton controls, composed) → optional dismiss icon-button (present when onDismiss is supplied). Variants (spec §3): verified / signal / caution / critical. States (spec §4): default only on the container; actions and dismiss carry their own interactive states via composed widgets. A11y (spec §7): container wrapped in VerdifyLiveRegion (Semantics(liveRegion: true)); on first appearance (in didChangeDependencies, post-frame) fires announceToAssistiveTechnology — assertive for critical (role=alert), polite for all others (role=status); status conveyed by icon + text, never color alone (WCAG 1.4.1) — the icon is ExcludeSemantics, the variant meaning lives in the body/title text; dismiss icon-button has accessible name "Dismiss" (MergeSemantics + Semantics(button:, label: 'Dismiss')); alert container is not a tab stop (spec §6) — only composed actions/dismiss are focusable; dismiss tap-target floor is v.targetMobile (44 px). Token bindings (spec §5, zero raw colors, all SPEC-correct): per-variant container tint colorStatus{Variant}Bg, border colorStatus{Variant}Border, icon color colorStatus{Variant}Fg; title colorTextPrimary at v.text.h3; body colorTextPrimary at v.text.body; dismiss glyph colorActionGhostFg; corner v.radiusMd. 16 component tests (body required, icon per variant, title optional, dismiss fires + named "Dismiss", dismiss absent when null, action fires, actions absent, color-not-alone text check per variant, liveRegion Semantics, critical liveRegion, polite variants not critical, not-a-tab-stop, iOS tap-target + labeled-tap-target, WCAG AA text contrast per variant). Golden (4 scenarios: verified body-only, signal title+body, caution title+body+dismiss, critical title+body+action+dismiss). Gallery section added to example/lib/main.dart. Barrel export: src/components/alert/verdify_alert.dart. Flutter limitation note: aria-describedby field-error association is a Web ARIA concept; in Flutter, pair the alert body text with the field's errorText (e.g. VerdifyTextField(errorText:)) — both surfaces state the same error, and AT reads both in traversal order. No Flutter API equivalent exists for aria-describedby; document this in the spec if wiring is expected.

  • VerdifySelect (anchored-overlay + roving + type-ahead consumer, P3.15): pick ONE option from a known, finite list (spec §1) — a closed combobox trigger in the tab order showing the value or placeholder, opening a listbox popup of option rows (optional leading icon + a NEUTRAL selected check) with optional group headings + optional help/error text. Controlled API: label (required, visible + associated — never replaced by the placeholder), options: List<VerdifySelectOption> (value + label + optional icon + optional group + disabled), value (selected option's value, or null) + onChanged, placeholder, description, error, enabled, loading, size (sm/md/lg) and width (auto/full). Built on the shared foundations: the listbox is a VerdifyAnchoredOverlay (autoFlip: true — a trigger near the screen edge opens the other way; NON-modal — the page behind stays live) and option navigation is a VerdifyRovingGroup (vertical roving tabindex, wrap: false per spec §6 — Down/Up move the active option by one with no wrap; Home/End jump to first/last enabled; disabled options skipped; the cursor seeds to the selected option or the first enabled) + the shared VerdifyTypeAhead (printable keys jump to the next matching label). Focus model — the focus-moving APG variant (documented decision): select.md §7 describes the aria-activedescendant model (DOM focus stays on the trigger, the active option tracked via aria-activedescendant). Flutter has no aria-activedescendant; the idiomatic + testable equivalent is the OTHER documented APG select-only-listbox variant — focus MOVES into the listbox (roving cursor on the active option) on open and RETURNS to the trigger on close, exactly as VerdifyMenu does and exactly as the React Select (radix-ui) does (see select.tsx's §7 focus-model deviation note + the React test "uses the focus-moving APG variant"). Both are documented APG variants; the keyboard model (incl. type-ahead), visible focus, name/role/value, and the target-size floor all hold. Sizes (spec §3, DEC-B): type role + vertical padding density ABOVE the shared target-size floor, never a fixed height below it — the option row's type role climbs caption → body → body-lg; the TRIGGER value, as a form field (DEC-A), holds the 16 px no-zoom font size on every size and climbs its role through the brand line-height + letter-spacing only. States: default · hover (trigger lifts to the secondary hover fill) · pressed · focus (2 px ring that PERSISTS while open) · open · disabled (out of the tab order, colorTextDisabled) · error (strong border + critical error text) · loading (non-blocking busy spinner in place of the chevron). Keyboard (spec §6): closed Enter/Space/Down/Up/Home/End open the listbox; open Enter/Space/tap commit the active option, close, and return focus to the trigger; Tab commits the active option then proceeds; Escape closes without changing the value and returns focus; type-ahead works while open. A11y (APG select-only combobox + listbox, spec §7): trigger is a combobox (Semantics(button: true) + expanded disclosure state + the label-plus-value as its accessible name, never the placeholder alone, folded via MergeSemantics); each option is MergeSemantics + Semantics(inMutuallyExclusiveGroup: true, selected:, enabled:, label:); group labels are header nodes; a disabled trigger reports aria-disabled and never opens; meetsGuideline(iOSTapTargetGuideline / labeledTapTargetGuideline / textContrastGuideline) on the trigger + the open rows. Token bindings (zero raw colors, control-* tier + neutral surface per spec §5; each verified SPEC-correct): trigger bg colorControlBg, border colorControlBorder (rest), colorBorderStrong (error — §5), colorBorderFocus (focus/open — §5), hover fill colorActionSecondaryBgHover, value colorControlFg, placeholder colorControlPlaceholder, chevron/spinner colorControlFg; listbox colorSurfaceRaised + colorSurfaceBorder + v.shadowMd + v.radiusMd; option label colorTextPrimary, active (highlighted) fill colorActionSecondaryBgHover (§5 — note this differs from Menu's colorActionGhostBgHover), selected check colorTextPrimary (a NEUTRAL mark, never a status color — §8), disabled option colorTextDisabled (DEC-C); group label colorTextMuted at v.text.label; error-slot text colorStatusCriticalFg (spec §5; note the React Select uses status-critical-on-surface — Flutter binds the SPEC token); description colorTextSecondary at v.text.caption; open fade v.motionDurationBase + v.motionEasingVerdify (instant under reduced motion — never the deliberate check duration). 18 component tests (placeholder→value, combobox + expanded, open/list, select-updates-closes-returns-focus, Down/Up roving + Enter commit, Home/End, no-wrap, skip-disabled, type-ahead, Escape no-change + return-focus, disabled non-open + aria-disabled, error-slot text + colorStatusCriticalFg, group label, selected-option aria-selected, guidelines) + a 2-group golden (select_closed sm/md/lg + selected + error + disabled; select_open the open listbox). Gallery section added to example/lib/main.dart. Barrel exports the public API (VerdifySelect, VerdifySelectOption, VerdifySelectSize, VerdifySelectWidth) via a show clause.

  • VerdifyMenu (anchored-overlay + roving + type-ahead consumer, P3.15): a popup list of ACTIONS a trigger opens (spec §1) — fires a command then closes. Declarative entry model (VerdifyMenuEntry sealed family): VerdifyMenuItem (label + optional leading icon + optional trailing shortcut hint + destructive/disabled + onSelected), VerdifyMenuGroup (a group-label heading over a set of items), VerdifyMenuSeparator (a decorative divider; rendered by VerdifyMenuSeparatorWidget), and VerdifyMenuSubmenu (a row that opens a nested popup of its own items). Built on the shared foundations: the popup is a VerdifyAnchoredOverlay (autoFlip: true — a menu near the screen edge opens the other way; NON-modal — the page behind stays live, focus is not trapped, Tab leaves) and item navigation is a VerdifyRovingGroup (vertical roving tabindex: Up/Down between enabled items, wrapping; Home/End to first/last enabled; disabled rows + group labels + separators skipped). Type-ahead uses the shared VerdifyTypeAhead foundation (a timed keystroke buffer, ~700 ms reset, that yields the next enabled item whose label starts with the typed string, skipping disabled — promoted from this widget's former inline buffer at Select, the second consumer). Reaching the roving cursor from the type-ahead key handler required a small foundation seam: VerdifyRovingController (added to roving_focus.dart; moveCursorTo(index) + cursor) so an external handler moves the cursor while keeping it consistent with arrow navigation (Select reuses it). A11y (APG menu-button + menu, spec §7): trigger is a menu-button (Semantics(button: true) + expanded disclosure state, folded via MergeSemantics); each item/submenu row is a menuitem (MergeSemantics + Semantics(button:, enabled:, label:)); a disabled item reports aria-disabled and is non-firing; group labels are header nodes, separators are ExcludeSemantics; Escape closes and returns focus to the trigger (a FocusScope around the caller's control); selecting an item runs its callback and closes; meetsGuideline(iOSTapTargetGuideline / labeledTapTargetGuideline / textContrastGuideline) on the trigger + the open rows. Submenu (spec §2): a VerdifyMenuSubmenu row opens its own nested VerdifyAnchoredOverlay to the side (Right / tap / Enter / Space opens; Escape / outside-tap closes and returns focus to the row); activating a nested leaf fires it and closes the whole menu. Token bindings (zero raw colors, neutral surface + destructive-as-the-one-color per spec §5): popup colorSurfaceRaised + colorSurfaceBorder + v.shadowMd + v.radiusMd; item label colorTextPrimary at v.text.body, active/hover fill colorActionGhostBgHover; destructive item label/icon colorActionDestructiveFg + active fill colorActionDestructiveBg; disabled colorTextDisabled (DEC-C, no blanket opacity); group label + shortcut hint colorTextMuted at v.text.label; separator colorBorderDefault; open fade v.motionDurationFast + v.motionEasingVerdify (instant under reduced motion — never the deliberate check duration). 16 component tests (open/render, menu-button + expanded, tap-activate-and-close, Down/Up roving + skip-disabled, Home/End, wrap, type-ahead match + skip-disabled, Escape + return-focus, disabled non-fire + aria-disabled, groups/separator, destructive color, submenu open + nested activate, guidelines) + a 2-scenario auto-open golden (menu_variants). Gallery row added to example/lib/main.dart. Barrel exports the public API via a show clause (internals stay private).

  • foundation/type_ahead.dartVerdifyTypeAhead (type-ahead foundation, P3.15): promoted from VerdifyMenu's inline _onTypeAhead at the second consumer (Select), per the build-on-brand-flutter extract-at-second-use rule. The reusable timed-keystroke nucleus for any list control whose printable keys jump the cursor to the next matching item (Menu, Select, future listbox/combobox). It owns ONLY the buffer + reset-timer mechanism and the match math — never any focus, roving cursor, or widget tree: handle(char, matchStrings:, from:, isDisabled:) appends the lower-cased char to the buffer, (re)starts a resetDelay timer (default defaultResetDelay = 700 ms), and returns the next index in matchStrings whose value startsWith the buffer (case-insensitive), skipping any isDisabled index and wrapping past the end (-1 when nothing matches). Match policy (WAI-ARIA type-ahead): a single repeated char CYCLES (the query collapses to that one char and the search advances from just past from, so pressing b repeatedly walks every b… item); a mixed multi-char buffer REFINES (the query is the full buffer and the search starts AT from, narrowing the current match); with no cursor yet (from < 0) the search starts at the first item (offset 0) so the first keystroke lands on the first match, not the second. VerdifyMenu now consumes it (all 18 Menu tests stay green, including type-ahead match + skip-disabled). 12 focused foundation unit tests (single-char-next, repeated-char cycle + wrap, multi-char refine, longer-buffer refine-to-later, case-insensitive, skip-disabled, no-match -1, empty-list -1, buffer-resets-after-delay via fakeAsync, fast-second-char-holds, default-delay constant, dispose-cancels-timer). Not exported from the public barrel (internal foundation, imported by consumers via src/foundation/). Binds no colors (pure logic); the gate scans lib/src/foundation regardless.

  • foundation/anchored_overlay.dartVerdifyAnchoredOverlay + AnchoredSide (anchored-overlay foundation, P3.15): promoted from VerdifyPopover's private _AnchoredOverlay at the second consumer (Menu), per the build-on-brand-flutter extract-at-second-use rule. The reusable NON-MODAL positioning + light-dismiss nucleus shared by every overlay whose panel floats next to a trigger and leaves the page behind LIVE (Popover, Menu, Tooltip, Select). A LayerLink joining a CompositedTransformTarget (trigger) to a CompositedTransformFollower (panel) inside an OverlayPortal; the side → target/follower anchor + gap-offset map; a TapRegion light-dismiss listener that observes outside taps WITHOUT swallowing them (the panel + trigger share a groupId so a tap on either is not "outside"); an UnconstrainedBox aligned to the resolved follower anchor so the panel sizes to its own content (else it fills the theater and swallows every tap, breaking the non-modal contract). Popover-review refinements applied: (1) the PopoverSide-typed param became the shared AnchoredSide enum (top/right/bottom/left, with opposite/isVertical) so Menu/Tooltip/Select position a panel without importing Popover — Popover keeps typedef PopoverSide = AnchoredSide so its public API is unchanged; (2) auto-flip on viewport overflow (autoFlip, default OFF) — reads the trigger's global rect (off a GlobalKey render box, more reliable than the LeaderLayer's parent-relative offset) + the panel's measured size + the view size, and flips to AnchoredSide.opposite when the preferred side lacks room and the opposite has more (Popover keeps it OFF as its YAGNI default; Menu turns it ON). API: link, controller, side, barrierDismissible, onDismiss, overlayBuilder, child, autoFlip, gap. VerdifyPopover now consumes it (all 12 Popover tests + golden stay green). 9 foundation tests: parameterized all-four-sides anchor mapping, auto-flip-at-edge, no-flip-with-room, autoFlip-off-stays, outside-tap dismiss, and the AnchoredSide.opposite/isVertical enum invariants. Not exported from the public barrel (internal foundation, imported by consumers via src/foundation/). Binds no colors (positioning only); the gate scans lib/src/foundation regardless.

  • foundation/roving_focus.dartVerdifyRovingGroup (roving-focus foundation, P3.15): promoted from VerdifyTabs's private _RovingTabList at the second consumer (Radio), per the build-on-brand-flutter extract-at-second-use rule. The reusable CORE keyboard + focus mechanism for any container that is ONE stop in the page tab order with arrow-key cursor movement (Tabs, Radio, Menu, Select, Sidebar). It owns ONLY the mechanism — a FocusNode per item, a FocusTraversalGroup(ReadingOrderTraversalPolicy) + per-item ExcludeFocusTraversal (the roving tabindex — only the cursor item is Tab-traversable), a Shortcuts/Actions handler for the orientation's arrow axis + Home/End (cross-axis arrows left UNBOUND so they bubble), wrap + skip-disabled navigation math, and the activation policy (manual = Enter/Space commit vs. selection-follows-focus = arrow commits). It owns NO chrome and NO layout: the caller supplies items via itemBuilder(context, index, node, focused) and the Row/Column/scroll via layoutBuilder(items), binding any visuals through VerdifyTheme itself. Tabs-review refinements applied: the cursor seeds from selected only when that index is enabled, else the first enabled item (so Tab never parks on a disabled/dead tab stop); an all-disabled group has no cursor (-1) and degrades gracefully — no tab stop, arrows are no-ops, no crash/hang. API: itemCount, isEnabled, orientation (Axis), wrap, manualActivation, selected (cursor seed), onActivate, itemBuilder, layoutBuilder, semanticLabel, debugLabelPrefix. VerdifyTabs now consumes it (keeping its canvas/divider chrome + H/V layout + horizontal scroll in the Tabs file via layoutBuilder); all 20 Tabs tests stay green. 16 focused foundation unit tests (arrows + wrap + Home/End + skip-disabled + manual-vs-auto + all-disabled + selected-on-disabled-seeds-to-firstEnabled). Not exported from the public barrel (internal foundation, imported by consumers via src/foundation/). Zero raw color literals (the gate scans lib/src/foundation).

  • VerdifyRadio (interactive form control + first roving-foundation consumer, P3.15): a radiogroup of mutually-exclusive options built on VerdifyRovingGroup. Anatomy (spec §2): VerdifyRadioGroup (group label + the roving group) + VerdifyRadioOption value objects (value + label + optional per-option description + disabled); the control is an outer ring with a filled inner dot when selected. Variants (spec §3): standard (vertical stack — the "Default" variant), withDescription (secondary caption line per option), card (bordered selectable surface; resting colorBorderDefault → hover colorBorderStrong → selected colorActionPrimaryBg). States: default · hover · pressed · focus (2 px ring) · disabled; selection (checked/unchecked) is orthogonal and applies in every state. Roving (spec §6): one tab stop into the group landing on the selected option (or the first enabled option when nothing is selected); selection FOLLOWS focus — arrows move-and-select the next/prev enabled option, wrapping (VerdifyRovingGroup with manualActivation: false and orientation: null to bind ALL FOUR arrows per the radio APG — Down/Right next, Up/Left prev); Space selects the focused option; disabled options skipped. A11y (spec §7): group Semantics(container:, label:); each option MergeSemantics + Semantics(inMutuallyExclusiveGroup: true, checked:, enabled:, label:); meetsGuideline(iOSTapTargetGuideline / labeledTapTargetGuideline / textContrastGuideline). Token bindings (zero raw colors): unselected ring border colorControlBorder (hover/pressed colorBorderStrong), control fill colorControlBg, selected ring + inner dot colorControlFg (neutral near-black ink per radio.md §5), disabled+selected dot colorTextDisabled (not colorControlFg behind opacity — mirrors the React radio's bg-text-disabled on the disabled+checked dot), focus ring colorBorderFocus (BoxShadow spread focusRingWidth), label colorTextPrimary, description colorTextSecondary, disabled label/ring colorTextDisabled (DEC-C, no blanket opacity), card border colorBorderDefault/colorBorderStrong/colorActionPrimaryBg, circle radiusFull, card corner radiusMd; dot appear/disappear via AnimatedScale(reducedMotion ? Duration.zero : v.motionDurationFast, v.motionEasingVerdify). Tap-target floor targetMobile (44 px). Note: Radio selection binds colorControlFg (ink), intentionally distinct from VerdifyCheckbox's brand-accent checked fill (colorActionPrimaryBg). A checkbox opt-in is a brand moment; a radio choice within a mutually-exclusive set is neutral (radio.md §5, also the React radio's peer-checked:border-control-fg). These are deliberately different — do not "reconcile" them. Gallery rows added to example/lib/main.dart.

  • VerdifyVerifiedBadge (motion-theatre exemplar + reduced-motion seed, P3.15): the ONE deliberate motion in the system — the verified check draws ONCE on first appearance via CustomPainter + AnimationController(duration: v.motionDurationDeliberate) + v.motionEasingVerdify, sweeping stroke progress 0→1 via PathMetric.extractPath; never loops or replays. Reduced-motion guard (seed for Skeleton/Spinner/Progress in Phase 3): when MediaQuery.disableAnimations is true, _controller.value = 1.0 is set in didChangeDependencies before forward() is called — the check appears fully drawn on the first frame and the controller never ticks, leaving no pending timers. Variants: size=sm/md (caption/body type role; 16/20 px check). States: default (at rest), reveal (the draw moment), reduced-motion (draw suppressed). A11y (spec §7): check is decorative (ExcludeSemantics); standard variant wraps with MergeSemantics so the label is the accessible name; icon-only (labelHidden=true) gets Semantics(image:true, label:ariaLabel) — mirrors VerdifyBadge icon-only pattern. Token bindings: colorStatusVerifiedBg/Fg/Border/Accent, radiusFull, motionDurationDeliberate, motionEasingVerdify — zero raw color literals. Gallery row added to example/lib/main.dart.

  • VerdifyToast (transient-queue + live-region exemplar, P3.15): VerdifyToastController (ChangeNotifier, imperative show/dismiss/dismissAll/pause/resume; IDs from internal counter — no external deps; auto-dismiss Timer stored per-entry; timer pauses on hover enter / resumes on hover exit using stored per-entry duration). VerdifyToastRegion host (Stack overlay, bottom-right; announces to AT via SemanticsService.sendAnnouncement on each new entry — assertive for critical, polite otherwise; births the live-region pattern inline, promoted to foundation/ at second use in Phase 3). VerdifyToast surface (slide+fade enter animation via v.motionDurationBase/v.motionEasingVerdify; Semantics(liveRegion: true); elevation v.shadowLg; bg colorSurfaceRaised; status-accent border + icon + text — never color alone; optional single action; dismiss icon-button with tooltip). Variants: verified/signal/caution/critical. All color/shadow/motion bindings from VerdifyTheme (zero raw literals). Gallery row added to example/lib/main.dart.

  • VerdifyPopover (non-modal anchored-overlay exemplar, P3.15): a floating panel a trigger opens next to itself, deliberately contrasted with the MODAL showVerdifyDialog (Dialog = ModalRoute + ModalBarrier/BlockSemantics + focus-trap; Popover = NON-modal, page stays live, focus NOT trapped). Anchored-overlay mechanism (the Phase-3 foundation/anchored_overlay.dart extraction template): the private _AnchoredOverlay joins a LayerLink CompositedTransformTarget (trigger) to a CompositedTransformFollower (panel) inside an OverlayPortal; the overlay child is wrapped in UnconstrainedBox so the panel sizes to its content (NOT the full theater — otherwise it absorbs every tap and breaks the non-modal contract); light dismiss is a TapRegion(onTapOutside:) listener that observes outside taps WITHOUT swallowing them, so the page behind stays hit-testable. API: trigger: (context, isOpen, toggle) builder (the one tab stop) + builder: WidgetBuilder panel content + side (top/right/bottom/left → follower anchor + 6px gap) + showArrow + barrierDismissible + enabled. Subcomponents: VerdifyPopoverHeader (title + optional close + optional body), VerdifyPopoverTitle (h3/colorTextPrimary, MergeSemantics+header:true so the titled panel reads as a labelled dialog-ish region), VerdifyPopoverBody (body/colorTextSecondary), VerdifyPopoverClose (neutral-ghost icon button, target floor targetMobile, invokes DismissIntent → the panel's Actions maps it to close — same callback as Escape/toggle), VerdifyPopoverArrow (decorative CustomPaint pointer, ExcludeSemantics). States: default · trigger hover/pressed/focus (the caller's control) · open (trigger exposes Semantics(expanded:) — the aria-expanded analog) · disabled (enabled:false, never opens). Keyboard: trigger toggles; on open focus moves into the panel (_panelFocusNode) without trapping (Tab leaves into the page — WCAG 2.1.2); Escape closes and returns focus to the trigger; click-away closes (light dismiss). Token bindings (spec §5, neutral surface only): panel fill colorSurfaceRaised; border colorSurfaceBorder; corner v.radiusMd; elevation v.shadowMd; title colorTextPrimary; body colorTextSecondary; close glyph colorActionGhostFg + hover colorActionGhostBgHover; arrow fill colorSurfaceRaised + edge colorSurfaceBorder. Open transition: a plain fade at v.motionDurationFast + v.motionEasingVerdify (_FadeIn), collapsed to instant under MediaQuery.disableAnimations (reduced motion) — never the deliberate check duration; exit is instant (controller-managed exit animation deferred with the auto-flip positioning YAGNI). A11y: trigger disclosure node carries expanded; title is an isHeader node; close is a labelled button; arrow is AT-hidden; meetsGuideline(iOSTapTargetGuideline / labeledTapTargetGuideline / textContrastGuideline). NON-MODAL proven by tests: a background control stays reachable while the panel is open (unlike a modal Dialog), and a page control after the trigger is focusable while open (focus not trapped). Gallery rows added to example/lib/main.dart. Golden auto-opens each variant into the overlay (popover_variants).

  • VerdifyTabs (roving-focus exemplar, P3.15): VerdifyTabsVariant (underline / pill) × VerdifyTabsOrientation (horizontal / vertical) × VerdifyTabsSize (md / sm) × VerdifyTabsActivation (manual / automatic). Controlled API — tabs: List<VerdifyTab> (label + optional leading icon + optional trailing count + per-tab disabled) + panels: List<Widget> + selected index + onChanged; one panel rendered at a time. Roving model: the tablist is ONE tab stop (FocusTraversalGroup + ReadingOrderTraversalPolicy), built as the self-contained private _RovingTabList (per-tab FocusNode, Shortcuts/Actions for arrow/Home/End, wrap-around _nextEnabled that skips disabled tabs) — the extraction template for the Phase-3 foundation/roving_focus.dart. Arrows move focus (Left/Right horizontal, Up/Down vertical); Home/End jump to first/last enabled; focus wraps; disabled tabs skipped. Manual mode (default): Enter/Space commit the focused tab; automatic mode: selection follows focus. States: default · hover (colorActionGhostBgHover) · pressed (no separate fill per spec §4) · focus (2 px ring via BoxShadow(colorBorderFocus, spreadRadius: focusRingWidth), DISTINCT from selection) · selected (indicator + FontWeight.w600 lift, not color alone) · disabled (colorTextDisabled, skipped in roving) · loading (panel busy spinner, tablist stays operable). A11y: Semantics(button:, selected:, label:) per tab; panel labelled by its tab; meetsGuideline(iOSTapTargetGuideline / labeledTapTargetGuideline / textContrastGuideline). Token bindings: selected indicator + pill fill colorActionPrimaryBg; pill fg colorActionPrimaryFg; selected text colorTextPrimary; unselected colorTextSecondary; disabled colorTextDisabled; hover colorActionGhostBgHover; focus ring colorBorderFocus; tablist/panel divider colorBorderDefault; surface colorSurfaceCanvas; pill corner v.radiusMd; indicator-slide via AnimatedContainer(v.motionDurationFast, v.motionEasingVerdify). Horizontal tablist scrolls (spec §7 focus-not-obscured) when tabs exceed width. Gallery rows added to example/lib/main.dart.

  • VerdifyCheckbox (tri-state, P3.15): VerdifyCheckboxVariant (standalone / parent) × VerdifyCheckboxSize (sm / md). Anatomy: box + indicator (check glyph or indeterminate dash) + label + optional description + optional error-text. States: default · hover (border-strong) · pressed · focus (2 px ring via BoxShadow(colorBorderFocus, spreadRadius: focusRingWidth)) · checked (action-primary fill + glyph) · indeterminate (parent-only resting state — dash glyph) · disabled (text-disabled, DEC-C fg-dim, no blanket opacity) · error (critical-border + critical-fg error text). Keyboard: Space and Enter toggle via Shortcuts/Actions wrapping FocusableActionDetector; indeterminate parent resolves to false on first Space. A11y: Semantics(checked:, mixed:) for tri-state; MergeSemantics folds label into the tappable node; tap-target floor v.targetMobile (44 px). Token bindings: box fill colorControlBg/colorActionPrimaryBg; border colorBorderDefault/colorBorderStrong/colorStatusCriticalBorder; glyph colorActionPrimaryFg; label colorTextPrimary/colorTextDisabled; description colorTextSecondary; error colorStatusCriticalFg; corner v.radiusSm. Gallery row added to example/lib/main.dart.

  • VerdifyCredentialCard (compose-a-committed-control exemplar, P3.15): one row in the credential list — seeds the molecule/composition template. Composition discipline: composes committed primitives via public constructors ONLY — VerdifyCard (static raised container), VerdifyButton (destructive remove), VerdifyCheckbox (selectable leading checkbox), VerdifyBadge (verified/primary status) — no internal duplication, no private reach-in; all focus rings, tap-target floors, keyboard models, and control animations come from those proven widgets. Anatomy: optional leading checkbox (selectable variant), optional kind icon (decorative, ExcludeSemantics), label (v.text.label + colorTextPrimary), identifier (v.text.mono + colorTextSecondary, wrapped in Directionality(ltr) isolate for RTL-safe display of addresses/hashes/wallet strings), optional status badges (verified green / primary neutral via VerdifyBadge), optional meta text (v.text.caption + colorTextMuted), required remove control (VerdifyButton.destructive), optional extra actions. Kind variants: email / phone / passkey / wallet / enterpriseSso. Selectable variant: VerdifyCheckbox(label: 'Select <credential>'). States: default, hover/pressed/focus (controls only — card surface is static), disabled (remove only, adjacent muted reason text), loading (remove shows CircularProgressIndicator, not re-triggerable — delegated to VerdifyButton(loading: true, onPressed: null)). A11y (spec §7): card is a Semantics(container: true) listitem; remove button semanticLabel is credential-specific ("Remove email jordan@example.com"); kind + identifier reach AT as text, never icon alone; checkbox label tied to credential. Token bindings (zero raw colors): container via VerdifyCard static (→ colorSurfaceRaised/colorSurfaceBorder/shadowSm/radiusLg); label colorTextPrimary; identifier colorTextSecondary; meta colorTextMuted; disabled-reason colorTextDisabled; status via VerdifyBadge (→ colorStatusVerified* / neutral); remove via VerdifyButton.destructive (→ colorActionDestructive*); checkbox via VerdifyCheckbox (→ colorControl*). Deviation from plan: uses Flexible (loose) instead of Expanded for the content block so the widget works in Alchemist's intrinsic-width golden context without overflow.

  • VerdifyCard (compound multi-slot, elevation consumer): VerdifyCardHeader, VerdifyCardBody, VerdifyCardFooter sub-widgets. Static variant = non-interactive grouping (v.shadowSm + colorSurfaceBorder). Interactive variant = button/link via FocusableActionDetector + GestureDetector; hover lifts to v.shadowMd + colorBorderStrong; pressed settles to v.shadowSm; disabled binds v.shadowNone + colorTextDisabled (DEC-C). Focus ring via BoxShadow(color: colorBorderFocus, spreadRadius: focusRingWidth). Transitions: AnimatedContainer(v.motionDurationFast, v.motionEasingVerdify). Corner radius: v.radiusLg (spec §5 --radius-lg). All elevation binds v.shadow* — no hand-authored BoxShadow lists. MergeSemantics folds header title into the interactive button node (WCAG 4.1.2). Gallery row added to example/lib/main.dart.

  • VerdifyButton, VerdifyTextField, showVerdifyDialog — the original foundation slice: the first three widgets, establishing the token-bound VerdifyTheme, the brand focus ring, the 44 px tap-target floor, and the modal-overlay pattern the rest of the library builds on.

Fixed #

  • VerdifyRadio — selected ring/dot binds colorControlFg (not colorActionPrimaryBg): radio.md §5 and the React radio both specify --color-control-fg (neutral near-black ink) for the selected outer ring and inner dot. The original Flutter port incorrectly used colorActionPrimaryBg (Sovereign Violet). Fixed to colorControlFg for selected+enabled; disabled+selected dot correctly uses colorTextDisabled (mirroring the React bg-text-disabled on the disabled+checked dot — not colorControlFg behind opacity). This is intentionally distinct from VerdifyCheckbox's brand-accent checked fill: a checkbox opt-in is a brand moment; a radio choice within a mutually-exclusive set is neutral.

  • VerdifyBadge — icon-only branch now applies IconTheme.merge so the icon renders with the status fg color instead of the ambient (black) color.

  • VerdifyBadge — empty-label a11y assert strengthened: label: '' without semanticLabel now fires the assert, closing a hole where an empty label passed the old label != null check but produced a Semantics(image: true, label: null) node.

  • VerdifyBadge — pill radius: BorderRadius.circular now uses v.radiusFull (9999) instead of v.radiusMd (8), matching spec §5 pill shape. Golden regenerated.

0
likes
150
points
23
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Verdify's Flutter design system: 36 token-driven, WCAG 2.2 AA widgets with light and dark themes, built on Material 3.

Repository (GitHub)
View/report issues

Topics

#design-system #ui #widget #accessibility #material-design

License

unknown (license)

Dependencies

flutter

More

Packages that depend on verdify_ui