AI indicators and thermal aura
Mark where AI produced or is producing something, with one warm visual identity.
Quick usage
Import the shared component; never copy its markup into a feature. The Web TSX tab beside the live stage has a complete, working example.
import Chip from '@/shared/components/Chip';
import AiSparkle from '@/shared/components/AiSparkle';
import { AiEmptyGreeting } from '@/shared/components/AiChat/AiEmptyGreeting';Live stage and code
The live component and its controls on the left, the code on the right; change a control and the code follows: Web TSX (the component in a working example), Tailwind and HTML (the markup it renders, with its data attributes), React Native, SwiftUI and Jetpack Compose. The header theme switch drives every stage.
Props
Read from the component's source when the docs build: names and defaults from its signature, types and descriptions from its JSDoc. A blank type means the prop has no JSDoc yet.
Chip
| Prop | Type | Default | Description |
|---|---|---|---|
tone | 'neutral'|'success'|'warning'|'error'|'info'|'violet'|'primary'|'outline'|'ai' | 'neutral' | The CONDITION being reported, not a colour choice (sec.13). `outline` is for a tag that names a thing rather than a state, so it spends no status hue. |
size | 'sm'|'md'|'lg' | 'sm' | The DENSITY of the surface, not the importance of the text: `sm` (20px) in table rows and grids, `md` (24px) on cards and panel headers, `lg` (32px) in inline banners only. The label is 12px at all three. |
variant | 'chip'|'badge' | 'chip' | `badge` is the counter (Inter, tabular figures), one rung tighter than a chip at every tier (18 / 22 / 30). |
dot | boolean | false | The 5px leading dot, in the chip's own colour. |
icon | React.ReactNode | null | A leading glyph instead of the dot. Sized by the caller, because Phosphor takes size as a prop; 12px is the tier for a 20px chip. |
trailingIcon | React.ReactNode | null | A glyph after the label, for a pressable chip that goes somewhere (an arrow to an external file). Same sizing as `icon`. |
count | number|string | null | A tally after the label ("Fasteners 4"). Rendered as a sibling so the chip's own gap spaces it, in the data face at the chip's own size. Do NOT nest a <Chip variant="badge"> inside a chip to get this. |
selected | boolean | false | The chosen one in a row of filter chips. Steps up the surface ladder rather than taking a status hue: a filter you picked is not a condition the thing is in. |
onClick | () => void | - | Renders a <button> and adds the mandatory 44x44 boundary. The chip does NOT grow to 28px - see the ds-chip note in the CSS. |
href | string | - | Renders an <a> instead, with the same boundary and hover. For a chip that GOES somewhere - a mailto, an external file - where a <button> with a programmatic navigation would lose the middle-click, the copy-link and the status bar a real link gives for free. |
className | string | - | - |
children | React.ReactNode | - | - |
...rest | - | - | Any other prop is passed through to the root element. |
Source: frontend/src/shared/components/Chip/Chip.js
AiSparkle
| Prop | Type | Default | Description |
|---|---|---|---|
size | number | 20 | Rendered px. 20 in the desktop cluster, 16 and 14 inline. |
tone | 'current'|'thermal' | 'current' | Paint. See above. |
className | string | '' | Hook for the host's hover/press treatment. |
...rest | - | - | Any other prop is passed through to the root element. |
Source: frontend/src/shared/components/AiSparkle/AiSparkle.js
AiEmptyGreeting
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | - | - |
hint | React.ReactNode | null | - |
prompts | string[] | [] | - |
onPick | (prompt: string) => void | - | - |
disabled | boolean | false | - |
Source: frontend/src/shared/components/AiChat/AiEmptyGreeting.js
DOM contract
What the rendered element carries: the data attributes its styles key on, its roles and ARIA, its focus style, and whether its stylesheet draws the 44px hit boundary. Also read from the source.
Chip
| Data attributes | data-selecteddata-sizedata-tonedata-variant |
| Roles | Native element semantics only |
| ARIA | None set by the component |
| Focus | Styles :focus-visible in its own stylesheet |
| 44px hit boundary | Yes: a ::after in Chip.module.css |
AiSparkle
| Data attributes | data-ds |
| Roles | Native element semantics only |
| ARIA | None set by the component |
| Focus | No :focus-visible rule of its own |
| 44px hit boundary | Not a pressable root |
AiEmptyGreeting
| Data attributes | None set by the component |
| Roles | Native element semantics only |
| ARIA | None set by the component |
| Focus | Styles :focus-visible in its own stylesheet |
| 44px hit boundary | Yes: a ::after in AiEmptyGreeting.module.css |
Accessibility and touch
The indicator is metadata; any control beside it has its own target.
AI indicators and thermal aura is passive: it reports a condition and is not pressed, so it has no hit boundary. If a design makes it pressable, it gains the 44 by 44 boundary and is marked /* ds-chip: why */.
Human in the loop
An AI mark is a claim about who wrote a value, so it has to stay true. These rules come from FRONTEND_PR_COMPLIANCE.md sec.16.1. Try the first one on the stage above: edit the vendor.
- Provenance clears the moment a person edits the value. The pill goes and the field edge returns to the standard focus, on --duration-instant, border and shadow only. There is no undo to the AI value and no confirmation prompt: a pill that survives a human edit claims the machine wrote something it did not.
- In code, Input takes origin="ai" or "human", and the caller flips it to "human" on the first edit. Nothing clears it automatically, so every AI-filled field must do this.
- Confidence is an affordance, never a gate. A confidence value never skips or short-circuits a human review step; high confidence is not approval.
- Confidence takes warm sand and terracotta, never a status hue. A green "high confidence" chip reads as verified, which is the one claim a confidence score cannot make.
- AI output is reviewed at the HITL review step, never in chat. An agent can name a match and describe an action, but never executes it from chat.
Do and avoid
Rules and constraints
Use it for
- AI-generated values, the assistant, a running pipeline.
- The AI pill (Chip tone="ai") on any value a model wrote, until a person edits it.
Avoid
- Never an ambient effect that changes size: a growing layer drags its edge (gate C14).
- Never a raw id in a status pill.
Constraints
- The four-point star, sand to terracotta; motion stops under reduced motion.
- Provenance clears the moment a person edits the value: the pill goes and the field edge returns to the standard focus, on --duration-instant. No undo to the AI value, no confirmation. Input takes origin; the caller flips it to human on the first edit.