Identity and media
How a record shows who and what it is — the avatar whose hue is derived from the name, who else is here (the presence stack and the badge on a focused item), the id chip, and the media header including its real no-image fallback.
Spec of record: design-system/components/data/Avatar.d.ts
Live
Real widgets, built from the real kit. Turn a knob and it rebuilds; the theme bar above applies the design system's own tokens — including anything you changed under Customize — and the device selector uses the same sizes the golden baselines gate on, so what you see here is what CI holds the widget to.
Press Focus on any preview to take it to the full window — at a desktop width that renders most devices at 1:1, which is when the widget's own controls become clickable. The bar always states the device and the scale, so a shrunk preview says it is shrunk. Esc leaves.
Dart for these values
family_identity() 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.
AppAvatar widget
The design-system Avatar — the identity block. A square at the chip and badge radius (DesignTokens.rXs) carrying a person's initials (or their image when one exists), sitting in the leading slot of thread cards, feed posts, tagged chips, and account rows.
Full dartdoc
The design-system Avatar — the identity block. A square at the chip and badge radius (DesignTokens.rXs) carrying a person's initials (or their image when one exists), sitting in the leading slot of thread cards, feed posts, tagged chips, and account rows. Identity, never status. Sanctioned size steps with their initial sizes: 32→11 · 28→10 · 22→9 · 20→9 · 16→8 (feed/compose · card/composer · reply · level-3 · tagged chip). Non-interactive; name is the semantic label. ```dart const AppAvatar(name: 'Adeyemi Okafor', size: 32) const AppAvatar(name: 'You', hue: AppAvatarHue.mono) AppAvatar(name: me.name, image: NetworkImage(me.avatarUrl)) ```
AppAvatardefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| hue | AppAvatarHue identity · mono | AppAvatarHue.identity | Identity hue (default) or the monochrome block. |
| image | ImageProvider? | null | Avatar image; cover-fit, initials fallback on failed load. |
| name * | String | — | Full display name — the initials source, the hash input, and the semantic label. |
| size | double | 28 | Edge length. Sanctioned steps: 16 · 20 · 22 · 28 · 32. |
AppPresenceStack widget
The faces of the OTHER people in a scope (`Presence.d.ts` PresenceStack): one AppAvatar each, a 2px gap and no overlap, at most max slots with the last one the `+N` overflow block. The whole stack is ONE button opening an anchored list that names every member and whether they are viewing or editing.
Full dartdoc
The faces of the OTHER people in a scope (`Presence.d.ts` PresenceStack): one AppAvatar each, a 2px gap and no overlap, at most max slots with the last one the `+N` overflow block. The whole stack is ONE button opening an anchored list that names every member and whether they are viewing or editing. Empty renders nothing. Pass it to `AppHeaderBar.presence` — the one place a floorplan shows presence: ```dart AppHeaderBar( title: 'Work order', presence: AppPresenceStack( members: ref.watch(presenceProvider(scope)).members, ), syncStatus: sync, ) ```
AppPresenceStackdefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| max | int | 3 | Slots before the overflow; the `+N` block takes the last one. |
| members * | List<PresenceMember> | — | The other people here, in display order. |
| size | double | 24 | Face size: sanctioned 24 · 28. |
AppPresenceBadge widget
"Sam Lee is editing" on an item another person has in focus (`Presence.d.ts` PresenceBadge): a 16 face and the sentence on the well plate. A list row, a form field and a markup annotation all use this one badge. label replaces the sentence; the face is unchanged.
AppPresenceBadgedefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| label | String? | null | A host's own wording, in place of the sentence. |
| member * | PresenceMember | — | The person holding the focus. |
AppIdChip widget
The design-system IdChip — how a record says its name. Renders id (e.g. `WO-2041`, `SR-1042`) in DesignTokens.dataS (the small mono/data face) on a faint DesignTokens.accentTint square, radius DesignTokens.rS.
Full dartdoc
The design-system IdChip — how a record says its name. Renders id (e.g. `WO-2041`, `SR-1042`) in DesignTokens.dataS (the small mono/data face) on a faint DesignTokens.accentTint square, radius DesignTokens.rS. This is the system's **ID grammar**: one per row, leading position — never a pill, never used for statuses (a status is a word per the constitution's "encode once" rule). Two modes: * **Static** (default, onTap null) — a plain labeled chip, not interactive. No Semantics button role, no focus/hover/press paint. * **Tappable** (onTap set) — some ID chips navigate to the record they name. Becomes a real focusable/tappable target with the state contract: enabled · hover (DesignTokens.ink4 wash, pointer enhancement only) · focus-visible (1px solid accent, zero offset) · pressed (1px translateY). The tap target is expanded to at least DesignTokens.touch even though the visual chip is compact. title feeds a Tooltip and, for the tappable form, the accessible name; for the static form it is exposed as a plain Semantics label. ```dart const AppIdChip(id: 'WO-2041') AppIdChip(id: 'SR-1042', onTap: () => openRecord('SR-1042')) ```
AppIdChipdefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| id * | String | — | The record ID text, e.g. `WO-2041`. Rendered in DesignTokens.dataS, one line, never wrapped. |
| onTap | VoidCallback? | null | Fires when the chip is tapped. Null renders the static (non-tappable) form — no Semantics button role, no hover/focus/press paint. |
| title | String? | null | Tooltip text and accessibility hint. Falls back to id for the Semantics label when null. |
AppMediaHeader widget
The design-system media header — a hero image band with a bottom scrim and slotted overlay content. Any surface, zero domain knowledge: image is a stored image reference fed verbatim to AppImage's resolution ladder (`staged://` / `asset:` / `http(s)://` / origin-relative / blank), and overlay is a…
Full dartdoc
The design-system media header — a hero image band with a bottom scrim and slotted overlay content. Any surface, zero domain knowledge: image is a stored image reference fed verbatim to AppImage's resolution ladder (`staged://` / `asset:` / `http(s)://` / origin-relative / blank), and overlay is a caller-built widget (title / status / actions) laid over the scrim. A blank/absent image renders the hairline-framed glyph placeholder — the band keeps its height so a layout does not jump between records with and without a photo. Anatomy (bottom to top): the image layer (over an DesignTokens.ink4 void, BoxFit.cover by default), a bottom-anchored scrim gradient (DesignTokens.scrim → transparent, enabled by default so overlay text stays legible over arbitrary photo pixels), the overlay slot inset from the band's start + bottom edges, and a bottom structural hairline framing the band off the content below. **Scrim.** The gradient rises from DesignTokens.scrim at the bottom to transparent at mid-band. Set scrim false to drop it (e.g. when the overlay carries its own background, or there is no overlay). With no overlay the scrim still paints when enabled — callers usually pass `scrim: false` there. **Placeholder.** When image is blank the band renders a centered DesignTokens.ink40 image glyph over an DesignTokens.paperW wash — the same "no image, identity only" grammar as the cell/gallery thumbs, at band scale. The overlay still renders over the placeholder. State/semantics: purely presentational — it renders and never navigates. When onTap is set the whole band becomes a button (e.g. open a full-screen viewer); the accessible name comes from semanticLabel. Reduced motion (reducedMotionOf — the framework flag OR the platform's own Reduce Motion) drops the Hero flight, and so does the desk posture, where navigation cuts. ```dart AppMediaHeader( image: observation.photoRef, // '' → framed placeholder heroTag: 'obs-image-${observation.id}', overlay: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(observation.title, style: onScrimTitleStyle), Text(status, style: onScrimSubStyle), ], ), onTap: openViewer, semanticLabel: 'Observation photo', ) ```
AppMediaHeaderdefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| fit | BoxFit | BoxFit.cover | How the image inscribes the band (default BoxFit.cover; pass BoxFit.contain to letterbox the full image over the DesignTokens.ink4 void). |
| height | double | _defaultHeight | The band height in logical pixels. Fixed so a layout does not jump between records with and without a photo. |
| heroTag | Object? | null | Optional Hero tag for the image layer. When set (and neither reduced motion nor the desk posture holds — at the desk navigation cuts, with "no flight at all") the photo flies between this band and the surface it opens — and, sharing one tag per record, between a list/map preview and the detail… |
| image | String | '' | The stored image reference, fed verbatim to AppImage's resolution ladder. Blank (the default) renders the framed glyph placeholder. |
| onTap | VoidCallback? | null | Tap callback — when set the whole band becomes a button (e.g. open a full-screen viewer). Null renders a non-interactive band. |
| overlay | Widget? | null | Overlay content laid over the scrim, inset from the band's start + bottom edges (a title, a status word, an actions row). Null renders no overlay. |
| scrim | bool | true | Whether the bottom scrim gradient paints (DesignTokens.scrim → transparent). Default true — keeps overlay text legible over arbitrary photo pixels. Set false when there is no overlay or the overlay carries its own background. |
| semanticLabel | String? | null | The accessible name for the band when onTap is set. Null falls back to a generic label. |
PhotoViewer widget
The full-screen zoomable photo viewer body — a plain paper surface under an AppHeaderBar close control, the image inscribed `contain` inside an InteractiveViewer (pinch/drag zoom up to 4x). Normally reached via openPhotoViewer; public so a host with its own route table can mount it there instead.…
Full dartdoc
The full-screen zoomable photo viewer body — a plain paper surface under an AppHeaderBar close control, the image inscribed `contain` inside an InteractiveViewer (pinch/drag zoom up to 4x). Normally reached via openPhotoViewer; public so a host with its own route table can mount it there instead. **No sync control here, deliberately.** The "every view carries it" convention names views; this is a lightbox over one image — a modal dismissed with an ✕, in the same family as a dialog or a sheet, none of which take the control either. The surface underneath it has one.
PhotoViewerdefault constructor
| Knob | Type | Default | What it does |
|---|---|---|---|
| imageRef * | String | — | The image's storage reference — any AppImage ladder shape. |
| title | String? | null | The header title; null renders the default. |
Where it lives
| Class | File |
|---|---|
| AppAvatar | packages/app_widgets/lib/src/kit/data/avatar.dart |
| AppPresenceStack | packages/app_widgets/lib/src/kit/data/presence.dart |
| AppPresenceBadge | packages/app_widgets/lib/src/kit/data/presence.dart |
| AppIdChip | packages/app_widgets/lib/src/kit/data/id_chip.dart |
| AppMediaHeader | packages/app_widgets/lib/src/kit/data/media_header.dart |
| PhotoViewer | packages/app_widgets/lib/src/patterns/photo_viewer.dart |