2× points on weekends · Gold tier
aiRA generated this promotion from your prompt. Review the diff and apply, edit, or reject.
The First Edition is organised around atomic design — Atoms → Molecules → Organisms → Templates — so every element maps cleanly onto a Figma library. The product has one assistant voice (aiRA), one set of avatars, and custom-rendered form controls throughout.
Open this file in a browser. No build step. Toggle Light/Dark and LTR/RTL via the top bar.
This single HTML file contains every part of the aiRA Design System First Edition — markup, tokens, behaviour, README, changelog, the Figma bundle (tokens.json in DTCG 2025.10, component map, Code Connect), and the system-wide Component status table. Open it in any browser; no build step, no external assets.
A standalone, browser-openable design system for the aiRA conversational product. Atomic IA, one assistant voice, no agent-identity fiction, and custom form controls throughout.
open aira-design-system/index.html
No build step. Toggle Light/Dark and LTR/RTL from the top nav.
aira-design-system/
index.html # full design system page tokens.css # all design tokens (light + dark) CHANGELOG.md # First Edition release notes README.md # this file assets/ # logo + fonts figma/ # tokens.json, figma-components.md, code-connect.json
Atoms Button · Icon button · Input · Textarea · Chip · Tag · Badge · Pill
Avatar · Citation chip · Tooltip · Kbd · Divider · Spinner · Skeleton
Molecules Field · Dropdown (single · multi · searchable × sm · md · lg) Checkbox · Radio (incl. disabled) · Toggle · Stepper · Progress Feedback control · Action row · Tabs · Breadcrumb · Popover Toast · Banner · Nudge card · Selection card
Organisms Chat message · Composer · Approval card (with Type-to-confirm) Code block · Inline diff · Agent activity (animated single-step + todo-style multi-step) · Conversation state machine · Modal · Sheet List row · Data table (column filters) · Card · Form · Change-log feed
Templates Empty · Loading · Errors · My Configs · Mobile · Email × 4
1. One aiRA. No named agents. No agent-config / qa / analytics / support / coder tokens, classes, badges, or avatars anywhere. 2. Custom-rendered form controls. Never the native <select>. 3. Stop, not disable. Agents are stopped via the composer Stop icon; they're never disabled.
Until the React component library lands:
index.html. Copy demo HTML verbatim into your product; replace inline values with your data binding.tokens.css. Import once at the app root. Never redefine a token locally.data-ds-* hooks at the bottom of index.html (Tabs, Dropdown, sidebar search, Type-to-confirm).The aiRA mark is a cluster of three four-pointed sparkles filled with the brand gradient (indigo-400 → sky-400). It does double duty as the app logo, the launcher icon, and the streaming / AI-generated indicator in product surfaces.
The sparkle (aira-sparkle.svg) is the smaller, in-product mark — used on AI-generated badges and as the launcher icon. The full logo (aira-logo.svg) is identical geometry at a larger render size.
Apply the gradient to the aiRA mark itself — and only the mark.
Use the gradient on a button, an input, a banner, or any large surface. It overpowers content.
For an action button, use the primary indigo (solid). For a streaming indicator, use the gradient on the sparkle mark only.
Replace solid status colours with the gradient. Status reads as state; gradient reads as brand decoration.
The mark scales from 12 px (inline meta) to 64 px (marketing hero). Keep the sparkle on a single stroke weight; do not redraw at small sizes.
Reserve at least 25% of the mark's height as clearspace around all four sides. Nothing intrudes — no text, no other glyphs.
#6268FF) → sky-400 (#3BA7FF), drawn across the bounding box of all three sparkles together. On dark surfaces, shift to the lighter pair: indigo-300 (#8E97FF) → sky-300 (#5EA8FF).Apply the gradient to the sparkle path itself. The mark is the gradient.
Place the sparkle inside a circular gradient background. The shape is the sparkle, not a chip.
Use the on-dark gradient (indigo-300 → sky-300) when the mark sits on a dark surface.
Render the sparkle in a single flat colour. The gradient carries the brand.
Lock the geometry — the same three-sparkle cluster at every size, on every surface.
Add a stroke, shadow, glow, or container around the logomark.
Mixed case — lowercase "ai" leading, uppercase "RA" trailing — locked as one token: aiRA. Set in Roboto Medium. Never reorder the case; never spell it "AIRA". The wordmark is live type, not artwork — it is set in the running font, never traced or outlined.
Render in --text-primary on light surfaces and #FFFFFF on dark or on the brand gradient. The gradient never fills the wordmark itself — it is reserved for the sparkle.
ai, uppercase RA — together, one token. Never AIRA, Aira, or aira.--fw-medium, weight 500). Letter-spacing -0.015em, line-height 1.--text-primary on light; #FFFFFF on dark or on gradient. No gradient fill, no stroke, no shadow.Set the wordmark at 18, 24, 32, 48 or 64 px font-size. Use the smallest size that remains comfortably legible on the surface.
Reserve clearspace equal to the cap-height of the "R" on all four sides of the wordmark. Nothing intrudes — no body copy, no buttons, no other glyphs.
Keep ai lowercase and RA uppercase, together as one token.
Spell it AIRA, Aira, or aira. The case is the brand.
Pair the wordmark with the sparkle in the combination mark whenever the layout allows it.
Fill the wordmark with the brand gradient. The gradient lives on the sparkle alone.
The horizontal lockup pairs the sparkle and the wordmark into a single asset (aira-lockup.svg). It is the default presentation in product chrome — top bar, sign-in, email headers.
Sparkle on the leading side, wordmark trailing, with a gap of --sp-2 (8 px). The sparkle's square dimension matches the wordmark's font-size in pixels (a 32 px wordmark pairs with a 32×32 sparkle; a 24 px wordmark pairs with a 24×24 sparkle). Optically centre the sparkle to the wordmark's x-height — the sparkle cluster extends roughly 1–2 px above the cap line and 1–2 px below the baseline.
--sp-2 (8 px) at wordmark sizes ≥ 24 px. --sp-1 (4 px) below.aira-lockup.svg.Keep the horizontal lockup intact at every size ≥ 20 px. Use the sparkle alone below that.
Increase the gap, stack the elements vertically, or wrap the lockup in a stroke or box.
Two-tier system: raw primitives at the base, semantic tokens on top. Product code references semantic tokens only.
indigo-600 (#3438C9) is the primary action colour — the fill used on every primary button across the system. Flagged below.
indigo-400 → sky-400. The single gradient used for the aiRA mark, send button, avatars, and any aiRA-anchored surface.
Reference semantic tokens (--text-primary, --bg-surface) in product code.
Reference primitives (--slate-700) directly. They change between modes.
Colors are authored in sRGB. Reference rendering is on a display calibrated to sRGB IEC61966-2.1, white point D65, gamma 2.2, at roughly 120 cd/m².
On uncalibrated panels — most Windows laptops, many external monitors — variation is expected in both light and dark mode. In light mode, panel white points drift cooler than D65, saturated mid-tones lose punch on sRGB-only panels, and hairline borders either vanish or harden. In dark mode, backlight bleed lifts near-black values into muddy grey-blue on IPS LCD while OLED renders them as nearly-off pixels. Four factors stack: display gamut (Mac displays cover P3; most Windows panels cover only sRGB), gamma curves, backlight characteristics, and browser color management (Safari respects the system ICC profile; Chrome and Edge on Windows are inconsistent).
#0E1424 — lifted slightly from true black so panels with backlight bleed render it as clearly dark rather than muddy grey-blue.Ambient lighting, blue-light filters, panel hardware, OS calibration, and browser color-management quirks are outside the system's reach. Aim for consistency within the variance envelope each panel allows.
Roboto, Regular (400) + Medium (500) + Semibold (600). One ramp, used consistently.
4 px base grid. Use the scale; no 10 px, 13 px, 22 px.
Three durations. prefers-reduced-motion halts animation — does not speed it up.
| Token | Value | Use |
|---|---|---|
--motion-fast | 120ms · ease-out | Hover, focus, micro-toggles. |
--motion-base | 200ms · ease-out | Default for most state changes. |
--motion-slow | 320ms · ease-out | Large surfaces (sheets, modals). |
--motion-reduced | 0.001ms | Detectable by JS when reduced-motion is on. |
Every icon used in the system lives here. 20 px nominal · stroke 1.5 · slate-600 default. Inherit colour from currentColor; never bake a colour into the SVG.
Every icon, when wrapped in an Icon button (or any interactive target), supports four states. The matrix below shows the contract on a representative icon — the same treatment applies to every icon in the grid above.
| State | Plus | Send | Stop | Trash |
|---|---|---|---|---|
| Default | ||||
| Hover | ||||
| Pressed | ||||
| Disabled |
Default · hover · pressed · disabled apply to every interactive icon. Disabled is reserved for "this action is not currently possible". For aiRA generation, use the Stop icon instead of disabling Send.
Render icons inline as SVG with stroke set to the current text colour, so the theme tints them.
Embed PNGs or bake hex colours into icons. Breaks dark mode.
Use the Stop icon (circle with a square inside) only when interrupting an in-flight aiRA generation.
Disable an agent. Agents stop generating; they're never disabled.
Five guidelines, the right encoding for every data relationship, and a gallery of every chart format aiRA produces. Based on Cleveland & McGill's perceptual rankings and Schwabish's Better Data Visualizations.
Encodings ranked by how accurately the eye reads them (Cleveland & McGill). Use higher-ranked encodings when precision matters.
| Rank | Encoding | Used in |
|---|---|---|
| 1 | Position on a common scale | Bar · Dot plot · Line |
| 2 | Position on non-aligned scales | Small multiples |
| 3 | Length without shared baseline | Stacked bar segments |
| 4 | Direction · slope | Slope chart |
| 5 | Angle | Pie chart (use sparingly) |
| 6 | Area | Treemap · Bubble |
| 7 | Volume | 3D — avoid |
| 8 | Colour saturation · hue | Heatmap · Choropleth |
| You're showing | Go-to chart types |
|---|---|
| Comparison across categories | Bar · Dot plot · Heatmap · Waffle |
| Change over time | Line · Area · Slope · Sparkline |
| Distribution | Histogram · Box plot |
| Relationship between variables | Scatterplot |
| Part-to-whole | Stacked bar · Treemap · Waffle |
| Precise value lookup | Data table (see below) |
The workhorse. Length on a common baseline reads accurately. Always start the value axis at zero. Sort by value unless a natural order exists.
Use horizontal when labels are long. Sort descending by default.
Two values per category, side by side. Limit to two groups; beyond that, switch to a slope chart or dot plot.
Total + composition. Only the bottom segment shares a baseline — put the most important there. Limit to three to five segments.
Bars extend in two directions from a central baseline. Use for data with a natural midpoint — satisfaction, sentiment, percent change.
Two dots per row, connected by a line. Shows the gap between two values across many categories — clearer than paired bars when the values are similar.
Grid of cells coloured by value. Two categorical axes, one quantitative measure. Sequential palette for a single measure; diverging when there's a midpoint.
10×10 grid of squares. Each square is one percent. More tangible than a pie for a single statistic — "23 of every 100 members".
The default for time series. Limit to four-to-six lines; beyond that, highlight-and-gray or small multiples.
When showing many series, color only the lines that carry the story. Everything else stays neutral.
Line chart with filled area below. Emphasises magnitude. Don't stack more than four-to-five series.
Two vertical axes connected by lines. Shows direction and magnitude of change across many categories at once.
Tiny inline chart — no axes, no labels. Used in tables and KPI tiles to add trend context to a single number.
Bars showing frequency per bin. Bars touch — gaps are reserved for categorical bar charts.
Median, IQR, and outliers in compact form. Requires statistical literacy — annotate parts for general audiences.
Dots on two continuous axes — the go-to for showing correlation. Use transparency when dots overlap; label outliers directly.
Nested rectangles, area encodes value. For hierarchical data. Area is imprecise — use for broad patterns, not lookup.
Only for two-to-three slices when the approximate proportion is the point. Never 3D, never exploded. With more slices, switch to a stacked bar or waffle.
Same constraints as a pie — two to three segments, single insight. The hole in the centre is a natural place to surface the headline number. Preferred over a pie when the dataset has a clear total worth calling out.
Unordered groups. Distinct, equally vivid hues. Limit to six.
WhatsApp is always green; Push is always indigo. Users learn the taxonomy once.
For ordered data (low → high). Single hue varying in lightness.
For data with a natural neutral midpoint — sentiment, percent change. Two hues diverging from neutral.
Green for positive change · orange for negative change. Red is reserved for errors and destructive actions — never the default for "down" deltas in a chart.
Tables are visualisations too. Right-align numbers, light horizontal rules only where needed, sparklines for trend context.
| Store | Sales | Δ vs prev | 6-mo trend |
|---|---|---|---|
| HSR Layout | ₹14,21,540 | +8.2% | |
| Indiranagar | ₹11,03,210 | +2.1% | |
| Koramangala | ₹9,84,720 | −4.6% | |
| Whitefield | ₹7,21,310 | −11.8% |
The title is the takeaway. "Active members grew 14% over six months" beats "Active members over time".
Start every bar chart's value axis at zero.
Truncate a bar axis to amplify the difference — it misleads.
Use orange for negative deltas. Red is reserved for errors.
Use a pie chart with more than three slices.
Highlight-and-gray when there are more than four to six series.
Use 3D charts or gradient fills on chart marks.
Six lifecycle states with paired surfaces.
Single-tone palette; no carnival-coloured highlighting. Tokens used in Code block.
| Token | Used for |
|---|---|
--syntax-keyword | SELECT, function, const |
--syntax-string | 'literal' |
--syntax-number | 42, 3.14 |
--syntax-comment | // remark |
--syntax-operator | =, +, AND |
--syntax-function | COUNT(), SUM() |
--syntax-punctuation | (), ;, , |
Honour the device notch and home indicator on mobile surfaces (sheets, app bars).
The hatched red zones map to env(safe-area-inset-*) on iOS and to WindowInsets (system bars + display cutouts) on Android. On the web, the same --safe-* tokens cover both platforms — the browser resolves them per OS. Apply the tokens as padding on any container that touches the viewport edge — sheets, app bars, full-screen modals — so content clears the notch, punch-hole, home indicator, and gesture-nav pill on every orientation.
| Token | Source (web) | iOS maps to | Android maps to |
|---|---|---|---|
--safe-top | env(safe-area-inset-top, 0px) | Notch / Dynamic Island | Status bar inset (incl. punch-hole) |
--safe-bottom | env(safe-area-inset-bottom, 0px) | Home indicator | Gesture nav pill (or 3-button nav) |
--safe-left | env(safe-area-inset-left, 0px) | Landscape notch side | Display cutout (landscape) |
--safe-right | env(safe-area-inset-right, 0px) | Landscape home-indicator side | Gesture nav (landscape) |
On Android WebView (Chrome 88+), env(safe-area-inset-*) resolves once the app sets WindowCompat.setDecorFitsSystemWindows(window, false) and the manifest declares android:windowLayoutInDisplayCutoutMode="shortEdges". The CSS contract is identical to iOS — write tokens, not platform branches.
Pad sheets, app bars, and full-screen modals with padding-top: var(--safe-top) and padding-bottom: var(--safe-bottom) so content clears the notch, punch-hole, home indicator, and gesture-nav pill.
Hard-code 44 px or 34 px insets. They differ per device, OS, and orientation — use the env-backed tokens.
Test on iOS (Dynamic Island + home indicator) and Android (status bar + gesture nav). The tokens resolve correctly only when the Android host turns off "fit system windows".
Branch CSS by user-agent. The same --safe-* tokens cover both platforms — branching means a missed device.
Same tokens, retuned for touch. Scale up the hit targets, drop the density, and keep the visual rhythm consistent across Windows, macOS, iPadOS, and Android.
Mobile-first. Layouts collapse top-down.
| Token | Range | Columns |
|---|---|---|
sm | 0 – 599 px | 4 |
md | 600 – 1023 px | 8 |
lg | 1024 – 1439 px | 12 |
xl | 1440 px + | 12 |
Minimum tappable area per surface. Always pass the largest constraint that applies.
Headings step down one notch on sm; body stays.
| Role | Web | Tablet | Mobile |
|---|---|---|---|
| Display | 40 / 48 | 32 / 40 | 28 / 36 |
| H1 | 28 / 36 | 24 / 32 | 22 / 30 |
| H2 | 22 / 30 | 20 / 28 | 18 / 26 |
| Body | 14 / 20 | 14 / 20 | 15 / 22 |
| Caption | 12 / 16 | 12 / 16 | 12 / 16 |
Tokens are platform-agnostic. The only platform-aware pieces are scrollbar width, native focus-ring colour, and font-smoothing — all handled by the design system shell.
| Concern | Web (Win / Mac) | iOS / iPadOS | Android |
|---|---|---|---|
| Tap target | 36 px | 44 × 44 pt | 48 × 48 dp |
| Body text | 14 / 20 | 15 / 22 (Dynamic Type) | 14 / 20 (sp) |
| Corner radius | --radius-md 8 px | 10 px (continuous) | 12 px |
| Elevation | --shadow-sm / md / lg | same shadow tokens | same + state-layer overlay |
| Focus ring | 2 px indigo + 3 px halo | system tint | 2 dp indigo |
| Iconography | 1.5 px stroke @ 20 px | 1.5 pt stroke @ 24 pt | 1.5 dp stroke @ 24 dp |
| Safe area | — | env(safe-area-inset-*) | system bars · 56 dp top |
How the aiRA chat surface restructures across breakpoints.
Use the same semantic tokens at every breakpoint. Only the hit-target size and the type-scale notch change.
Re-skin a component per breakpoint. The tokens already handle theme; breakpoints only resize.
Bump every tappable target to ≥ 44 px on mobile and ≥ 48 dp on Android.
Ship a 28 px target outside a dense table. 28 px is the floor for opt-in density only.
Voice is part of the design. Nine hard rules.
"Couldn't run that query — the table isn't connected yet. Connect it under Settings → Data."
"Sorry, sorry — I'll try again." Reflexive apologies clutter the surface.
"aiRA created 1 promotion." Numerals scan faster than spelled-out one.
"aiRA thought you might like this!" Reads as artificial enthusiasm.
can't / can / nextWhen aiRA can't fulfil a request, the response has three parts: what it can't do, what it can, and what to do next.
Four variants, three sizes. Primary indigo-600 fill. The single canonical button atom — no per-context one-offs.
| Prop | Values |
|---|---|
variant | primary · secondary · tertiary · danger |
size | sm · md · lg |
disabled | boolean |
Use primary for the single confirming action; secondary for the alternative; tertiary for low-frequency.
Stack two primary buttons next to each other. There can only be one.
Match button size to the surrounding density.
Use a danger button for routine destructive actions like Remove — use secondary.
Single-icon affordances. 32 px default; 44 px touch. Use the Stop icon only for halting an in-flight aiRA generation.
Filled square inside a coloured circle. Indicates active generation; clicking it stops aiRA.
Pair every icon button with an accessible name describing the action.
Pack more than three icon buttons in a row without visual separation.
36 px default. Ten states.
Show a label above the input. Placeholder is never a substitute for a label.
Trigger validation on every keystroke. Validate on blur or submit.
Pre-fill the most likely value when one exists.
Use a generic Save error — name the field that failed.
Multi-line input. Resizable on the Y axis only.
Resize vertically only.
Auto-grow past six lines without showing a scroll affordance.
Neutral metadata tag, removable. Carries an optional dot for status semantics.
Reserve colour semantics — green for success, amber for pending, red for danger.
Stack chips, tags, and badges in the same row — pick one primitive per context.
Categorical label. Larger surface than a chip; no dot.
Use Tag for categorical grouping (channel, type, owner).
Use Tag for status — that's a Pill.
Count or numeric indicator. Sits on top of a parent surface.
Cap counts at 99+. Beyond that, the precise number is noise.
Use a badge for a state name — that's a Pill.
Lifecycle states across approvals, scheduled jobs, and content. Six variants.
| Variant | Token pair |
|---|---|
draft | --approval-draft-bg · --approval-draft-text |
awaiting | --approval-awaiting-bg · --approval-awaiting-text |
upcoming | --approval-upcoming-bg · --approval-upcoming-text |
approved | --approval-approved-bg · --approval-approved-text |
live | --approval-live-bg · --approval-live-text |
rejected | --approval-rejected-bg · --approval-rejected-text |
Use the canonical state colours so users learn the lifecycle once.
Invent new state labels — extend the existing set instead.
Two variants only — User initials and the aiRA mark. Both use the canonical aiRA gradient (--avatar-grad). No per-agent avatars: the product has one assistant voice.
Use the aiRA avatar wherever the assistant talks. Use the User avatar for the human user; never for aiRA.
Tint avatars by "agent type". There is only one aiRA.
Provide aria-label when there's no visible name.
Use random colours for user initials. The system has one avatar gradient.
RAG provenance. Real <a href> links. Hover lifts the border + text to brand.
Tier upgrades happen automatically when 90-day spend crosses the threshold 1Tier upgrade SOP. The system re-evaluates eligibility nightly 2Loyalty engine spec.
Hover or keyboard-focus brightens the border (--citation-border-hover) and shifts the text to brand colour (--citation-text-hover).
Customer 88959517 wasn't issued points because the transaction amount was below the promotion's minimum threshold.
Number citations sequentially in reading order.
Cite more than four sources inline. Use the appended list.
Short explainer on hover/focus. Surface is --tooltip-bg (slate-700 — softer than near-black) with white text at 9.6 : 1 contrast.
Use Tooltip for short, optional context. Trigger on hover AND focus.
Hide essential information inside a tooltip. Surface it inline.
Visualises a key in a shortcut. Use sparingly — inline help, command palette docs, the Selection card keyboard hint.
1 px horizontal rule using --border-default.
Indeterminate progress. 14 px default.
Placeholder for loading content. Hide from assistive tech (aria-hidden).
Composed label + control + helper + error. The shape every form control uses.
label[for] ↔ input[id].aria-describedby to associate the helper with the input.aria-describedby in addition to the visual border colour.The canonical custom-rendered form-select molecule. Three variants (single · multi · searchable) × three sizes (sm · md · lg) — maps directly onto a Figma 3 × 3 variant matrix. Never use the native <select>; behaviour is fully controlled in CSS and JS.
Single-choice selection. Caret on the right; menu opens below.
Checkbox rows inside the menu; trigger shows a count chip. Closing commits the selection.
Input-driven; type to filter. The menu shows a search input above results when the option set is large.
Open menus sit at z-index: 40 over sibling content. Max-height is 280 px; longer lists scroll internally. Menus never push the section below off-screen, and they always render above later content — fixing the v2 combobox overlap bug.
| Prop | Values |
|---|---|
variant | single · multi · searchable |
size | sm · md · lg |
state | closed · open · loading · empty · disabled · error |
options | Array of { id, label, sublabel } |
onChange | Single: option id. Multi: array of ids. |
aria-haspopup="listbox" + aria-expanded.role="listbox" with aria-multiselectable on multi.role="option" with aria-selected.Use sm in dense tables; md as default; lg only in marketing forms.
Default to 'Select…' when a sensible pre-pick exists.
Cap the open menu at the molecule's max-height; never let it cover the next section.
Use multi-select when the user picks one of two — that's a Toggle.
Multi-select within a group. Wrap with <fieldset> + <legend>.
Wrap a group with a fieldset and a legend so the group has a name.
Use a single standalone checkbox for binary on/off — that's a Toggle.
Exclusive selection. Always wrap in <fieldset> + <legend>. The disabled state is documented explicitly.
Always pre-select the safest, most-common default.
Use radio for more than five options. Switch to a Dropdown.
Binary on/off that takes effect immediately. For settings that need an explicit Save, use a Checkbox in a form.
Reflect immediate effect — no Save button.
Use a Toggle when the change needs a confirmation. Use a Modal.
Ordered multi-step flow.
Determinate progress for known durations.
Two bare icon buttons — thumbs-up / thumbs-down — sitting inline with the action row. No pill, no container, no divider. The selected thumb fills with the link colour. An optional reason picker pops below on Not helpful.
Render the thumbs as bare icon buttons, sitting inline with the action row. No pill, no border, no divider.
Wrap the controls in a chip, pill, or bordered container. The icons themselves are the affordance.
Open a reason picker on Not helpful. The signal alone is half useful.
Block the user with a modal reason prompt. Keep it inline and dismissible.
Inline actions under an aiRA reply — Copy · Support · Feedback (thumbs-up / thumbs-down). Keyboard focus uses the global :focus-visible ring; no per-action override.
Local navigation between views of the same data.
Hierarchical location indicator. Useful inside multi-step configurators.
Rich content with actions. Footer buttons are right-aligned; primary on the far right.
The Churn Propensity model rates this customer at 0.74 based on recency (45 days), basket-size decline (−32% qoq), and no email opens in 14 days.
Popover footer actions are right-aligned. Primary action sits on the far right. Tertiary or destructive actions sit to its left.
Right-align footer actions; primary on the far right.
Hide essential information in a popover. Surface it inline.
Transient notification. Auto-dismisses; reserves for non-critical confirmations.
Auto-dismiss after about four seconds.
Stack more than three toasts. Collapse the rest into a notification centre.
Inline page-level message. Persistent until dismissed.
Match the banner colour to severity — info, warning, danger, success.
Use a banner for transient feedback. That's a Toast.
Suggestion or prompt presented next to the chat surface. Blue left-border accent.
Render adjacent to the chat surface, never on top of it.
Stack two nudges. One per session.
Disambiguation step. Keyboard-first, escape-to-skip, free-type fallback. Option rows and the "Something else" row use visually distinct hover treatments so the two never look like the same target.
Option rows and "Something else" use different hover surfaces so they read as separate affordances.
| Element | Default | Hover | Focus (keyboard) | Selected |
|---|---|---|---|---|
| Option row | transparent | --bg-hover | focus-visible ring | --bg-selected + filled number badge |
| "Something else" | transparent, 1 px top divider | --bg-accent-subtle (distinct violet tint) | focus-visible ring | n/a (escape hatch) |
| Skip button | tertiary chip | --bg-hover background | focus-visible ring | n/a |
| Prop | Values |
|---|---|
title | The question being asked |
options | Array of 2–5 strings |
step | { current, total } when chained |
allowSomethingElse | boolean |
onSelect | Fires on click, Enter, or numeric key (1–5) |
onSkip | Fires on Skip click or Esc |
Use when the prompt has 2–5 likely interpretations. Numbered options are faster than prose.
Use for more than 5 options. Switch to a Dropdown (searchable).
Always include "Something else / Skip". Never force the user into a listed option.
Block the composer while a Selection card is up. Typing dismisses the card.
Interactive checklist with check-off. Items strike through and fade once complete. Used in onboarding, multi-step setup flows, and anywhere a small set of discrete tasks needs visual progress.
input[type="checkbox"].Use for short, ordered lists (3–8 items) where each row is a discrete task.
Use for long, unstructured task lists. Past ~8 items, switch to a Data table with status.
Animate the strike-through on toggle (200 ms). Builds visual confirmation.
Hide completed items by default. Crossed-out items show progress and remind the user what was done.
Three message types. aiRA's reply free-flows — anchored by the aiRA avatar at the start of the message. No agent label, no badge, no chip.
role="log" aria-live="polite"; new messages announce.role="status" aria-live="polite".role="alert".Let aiRA's reply free-flow as plain text. The avatar is enough attribution.
Wrap aiRA's reply in a bubble or attach an agent-name badge. There are no agent labels.
Input area with attach, voice, send. While aiRA is generating, the send icon swaps for the Stop icon — clicking it interrupts generation and keeps the partial output. Agents are never disabled; they're stopped.
The Send button stays disabled until the user types. Disabled uses the neutral disabled surface; the gradient never lives on this control.
Once the input has content, Send enables and renders in the primary action colour (indigo-600). No gradient — gradients are reserved for the aiRA mark.
The send icon is replaced by the enabled Stop icon. Placeholder reads "Generating…". Clicking Stop transitions the conversation state to aborted; partial output is preserved.
For historical or shared sessions where the user can read but not send.
Swap the Send icon for the Stop icon while aiRA generates.
Disable the composer because aiRA is busy. Let the user keep typing.
aiRA proposal awaiting human review. Header is the proposal title only — no agent badge. High-risk variants use the Type-to-confirm pattern.
aiRA generated this promotion from your prompt. Review the diff and apply, edit, or reject.
Six lifecycle states. Status pill on the left of the footer; primary action shifts per state.
Draft — not yet submitted for approval.
Submitted 4 min ago · with Priya R. for review.
Approved by Priya R. · scheduled to go live Sat at 00:00.
Approved by Priya R. · 2 hours ago.
Live since Sat 00:00 · 1,247 redemptions so far.
Rejected by Priya R. · 2 hours ago.
Production-affecting, irreversible, security-relevant, or financial changes require a secondary confirmation. On Accept click, the footer Accept button swaps in place for a slim input (placeholder "Type APPROVE to confirm") + a Confirm button — both disabled until the input matches APPROVE (case-insensitive). Reject and Edit remain reachable. Same compact primary-button size as routine Accept — no giant outsized button.
Applies to all 1.2 M members; irreversible once live. Confirm by typing APPROVE.
| Lines in diff | Treatment |
|---|---|
| ≤ 12 | Render in full. No scroll, no collapse. |
| 13 – 40 | Max-height 320 px with internal scroll. Caption above the diff: "12 of 40 lines visible". |
| > 40 | Collapse to a summary (N added · M removed · K changed) with "View full diff" opening a Modal. Approval cannot complete until the full diff has been opened. |
| Binary payload | Replace diff with a before/after preview. Never paste raw bytes. |
audience or schedule expands. Surface a risk pill in the header.The status pill colours and the lifecycle states (Draft → Awaiting → Upcoming → Approved → Live → Rejected) are wired to Capillary's Connect+ workflow. White-label / reseller deployments need a --approval-state-* token layer that maps tenant lifecycle names to the system's six-state machine. Tracked under Deferred · Multi-brand theming hooks; flagged here so the override surface is designed in, not bolted on later.
Show the diff or preview inline up to twelve lines; collapse beyond that.
Reuse the routine Accept styling on high-risk proposals. Use Type-to-confirm.
First-class output for SQL and scripts. Language badge, Copy / Explain actions.
function normaliseRow(row) { // Drop fields not in target schema const { id, amount, currency } = row; return { id, amount: Number(amount), currency: currency || 'INR' }; }
Add / remove / context lines. Used inside Approval cards and the Change-log feed.
Pair sign and colour — plus for added, minus for removed.
Hide context lines. Surrounding context tells the reviewer where the change lands.
Streaming disclosure of what aiRA is doing. Use the single-step row for one task; the multi-step list when several steps run in sequence.
One row: spinner + step verb + optional detail. Use plain verbs — no internal job IDs.
Each step starts empty; the indicator fills on activation and locks to a check mark when complete. Completed rows strike through with faded text, like ticking items off a list.
Use plain verbs. "Querying analytics DB" — not "Executing query_engine_v3".
Surface internal job IDs.
Show duration on completed steps.
Spin forever. Past 10 s, append "Still working — taking longer than usual" inline.
An AI turn has seven stops. The table below maps every state to its visible UI, user affordance, and live-region behaviour.
| State | Visible UI | User affordance | Live region |
|---|---|---|---|
| idle | Composer focused; send button disabled (empty input). | Type to start. | None. |
| user-sent | User message renders end-aligned. Composer clears, send returns to disabled. | Continue typing follow-ups. | Thread role="log" announces. |
| thinking | Activity row above the composer — spinner/sparkle + step verb. | Cancel on the activity row (Esc alias). | role="status" aria-live="polite". Step changes announce. |
| streaming | aiRA message renders; tokens append in place. Composer Send is replaced by the enabled Stop icon. | Click Stop to interrupt (preserves partial output). | Body role="status"; tokens announced in chunks ≥ 80 chars. |
| done | Final reply renders; Action row appears (Copy · Support · Feedback). | Compose a follow-up — loops back to user-sent. | Live region settles. |
| error | Error message in the danger surface. Plain-language reason. | Retry (rewinds to user-sent with original prompt). Optional Report. | role="alert" — interrupts. |
| aborted | Partial reply stays; system line: "Stopped at your request." | Continue from here · Regenerate · ask a new question. | System line not announced. |
For confirmation, destructive actions, and short forms.
Use when the user is about to do something destructive, irreversible, or financially material. Icon-led headline, plain-language consequence, danger primary action.
alertdialog (not dialog) so assistive tech announces it as a warning.Use sparingly — reserve for confirmation or short forms.
Nest modals inside modals.
Mobile escalation of Modal. Honours --safe-bottom.
Pad the bottom with safe-bottom so the action row clears the home indicator.
Stack content beyond 95% height — route to a page instead.
Chat history rail — a narrow sidebar pinned to the left of the chat surface listing previous sessions (ChatGPT / Gemini convention). Each row is a single click target. An export marker on the right shows how many times the session was exported (FTP push, CSS download, or workbook download).
--bg-selected with a 3 px --border-focus leading border.Wrap the whole row in a single interactive target. The export marker is a tooltip, not a separate link.
Split actions into multiple click targets on the same row.
Show one pip per distinct export type. The count carries multiplicity.
Repeat the same pip three times for three exports of the same kind. The pip is the type; the count is the volume.
Every column header carries the same sort icon — chevrons-up-down. A click cycles ascending → descending → unsorted. The icon never changes shape; only its colour reflects state (slate-500 idle, indigo-600 active). aria-sort on the header carries the order for assistive tech. No filter icons in headers.
| Store | Sales | Txns | Δ vs prev 30d | Status |
|---|---|---|---|---|
| HSR Layout | ₹14,21,540 | 3,219 | +8.2% | Active |
| Indiranagar | ₹11,03,210 | 2,884 | +2.1% | Active |
| Koramangala | ₹9,84,720 | 2,440 | −4.6% | Active |
| Whitefield | ₹7,21,310 | 1,940 | −11.8% | Upcoming |
One icon, three states. Shape stays the same on every header; colour and aria-sort carry the meaning.
| State | Icon shape | Icon colour | aria-sort |
|---|---|---|---|
| Unsorted (idle) | chevrons-up-down | --text-tertiary (slate-500) | none |
| Ascending (active) | chevrons-up-down | --text-link (indigo-600) | ascending |
| Descending (active) | chevrons-up-down | --text-link (indigo-600) | descending |
Use the same chevrons-up-down icon on every column header. Consistency reads as one control, repeated.
Swap to a triangle, caret, or directional arrow when a column becomes active. Shape change adds visual noise.
Right-align numbers; use font-variant-numeric: tabular-nums.
Use red for negative deltas. Orange (delta-negative) is the chart palette default.
Wire aria-sort on the header — assistive tech reads it.
Pair a separate filter icon with the sort. The data table sorts; it doesn't filter.
Generic container. Use only when the content needs visual elevation; otherwise prefer a plain section.
1,247 active members · 38% redemption rate · 12% new tier upgrades.
Use Card only when the content needs elevation; otherwise a plain section is enough.
Nest cards inside cards.
Standard composition — field stack + actions footer.
Keep the action footer right-aligned; primary on the far right.
Hide the submit affordance below the fold.
A product UI pattern for visualising change history inside aiRA — e.g. a loyalty-program edit timeline, a Config audit trail, or "What changed since you last looked." Vertical timeline with grouped entries.
audience; scope narrowed back to Gold tier.auto_apply flag from program settings.Group entries by day; lead with actor and change type.
Include entries with no summary. Every entry must say what changed.
Specific copy that names what's missing and offers a next step. Never a generic "No results".
Try a shorter query, or search by promotion ID. You can also reset filters to see everything in this brand.
Three loading patterns. Choose by the work being done.
Errors are first-class UI. Plain-language reason, then what to do next. Never a stack trace.
A list of saved configurations the user can reuse — list-row organism with a leading aiRA avatar, the config name, and a status pill.
Desktop is the primary surface; mobile honours safe-area tokens for sheets and bottom CTAs.
Tokens applied to the aiRA surface. The same theme switch flips every element below — sidebar, header, tabs, metric cards, alert — with no layout shift and no contrast loss. Toggle the Dark button in the top bar and watch the cascade.
Every surface above is composed from semantic tokens — --bg-surface, --bg-surface-alt, --bg-selected, --border-default, --text-primary, --text-tertiary, --text-link, --delta-positive, --delta-negative. Toggling the theme on the top bar re-resolves every one of those — no per-component override needed.
Four transactional templates aiRA sends. Shared rules apply to all four — listed once below.
tokens.css at build time.Sent when a user shares a chat session with a colleague.
Hi Punya,
Punit (punit.sharma@acme.com) has invited you to view a chat session.
| Open session |
Sent when a reviewer is asked to approve an aiRA proposal.
Punya created this promotion using aiRA. It's waiting on your approval before it goes live this Saturday.
| Review |
If you can't get to this today, reply with defer 24h and aiRA will resend tomorrow.
Sent when a scheduled aiRA job completes (daily / weekly digest).
Your scheduled summary is ready.
|
Active members
12,481
+4.2%
|
Redemption rate
38%
−1.1%
|
|
|
Tier upgrades
219
|
Top store
HSR Layout
|
|
| Open in aiRA |
A morning summary of pending items the user owns.
Render diffs as text in the email. Survives mobile zoom, alt-text fallback, and assistive tech.
Send a "click to view in browser" stub. Put the decision in the email.
WCAG 2.2 AA+ as the floor. Contrast verified token-by-token, light + dark.
| Pairing | Ratio | WCAG |
|---|---|---|
text-primary on bg-surface | 15.8 : 1 | AAA |
text-secondary on bg-surface | 9.6 : 1 | AAA |
text-tertiary on bg-surface | 5.0 : 1 | AA |
btn-primary-text on btn-primary-bg | 8.2 : 1 | AAA |
tooltip-text on tooltip-bg (slate-700 surface, white text) | 9.6 : 1 | AAA |
citation-text-hover on citation-bg-hover | 7.4 : 1 | AAA |
status-upcoming-text on status-upcoming-bg | 5.6 : 1 | AA |
| Pairing | Ratio | WCAG |
|---|---|---|
text-primary on bg-surface | 14.1 : 1 | AAA |
tooltip-text on tooltip-bg (slate-100 surface, slate-900 text) | 14.0 : 1 | AAA |
btn-primary-text on btn-primary-bg | 7.4 : 1 | AAA |
status-upcoming-text on status-upcoming-bg | 7.2 : 1 | AAA |
focus-visible on every interactive element.prefers-reduced-motion halts animation (sparkle, todo strike-through, shimmer).for/id or aria-labelledby; errors via aria-describedby.aria-label.Five surfaces for telling the user something. Pick by urgency and persistence.
| Surface | Use when… | Persistence |
|---|---|---|
| Toast | Non-critical confirmation that the user can ignore. | Auto-dismisses ~4 s. |
| Banner | Page-level state that should persist. | Stays until dismissed or condition changes. |
| Nudge card | Suggestion adjacent to the chat surface; user can act or ignore. | Stays until dismissed. |
| Inline error | Field-level validation. | Stays until the field is valid. |
| Modal | The user must act before continuing. | Blocks the page until resolved. |
aiRA is a B2B power-user tool. Cmd+K and slash commands are expected conventions. The system doesn't ship a palette component yet because the underlying command surface isn't designed. Until that's decided, do not ship a Cmd+K UI per team — the design will fragment. Tracked under Deferred.
The React component library is correctly deferred. Until then:
index.html. Copy demo HTML verbatim; replace inline values with your data binding.tokens.css. Import once at app root. Never redefine a token locally.data-ds-* hooks inline at the bottom of this file (Tabs, Dropdown, sidebar search).When the React library lands, the migration is a class-rename pass + a token resolver swap — not a rewrite.
System-wide parity. Figma means a 1:1 Figma library component exists. HTML means a live reference renders in this file. A11y means keyboard, ARIA, and contrast have been audited in light + dark for the latest spec.
| Component | Figma | HTML | A11y | Status |
|---|---|---|---|---|
| Button | ✓ | ✓ | ✓ | |
| Icon button | ✓ | ✓ | ✓ | |
| Input | ✓ | ✓ | ✓ | |
| Textarea | ✓ | ✓ | ✓ | |
| Chip | ✓ | ✓ | ✓ | |
| Tag | ✓ | ✓ | ✓ | |
| Badge | ✓ | ✓ | ✓ | |
| Pill (status) | ✓ | ✓ | ✓ | |
| Avatar | ✓ | ✓ | ✓ | |
| Citation chip | ✓ | ✓ | ✓ | |
| Tooltip | ✓ | ✓ | ✓ | |
| Keyboard (kbd) | ✓ | ✓ | ✓ | |
| Divider | ✓ | ✓ | ✓ | |
| Spinner | ✓ | ✓ | ✓ | |
| Skeleton | ✓ | ✓ | ✓ | |
| Field | ✓ | ✓ | ✓ | |
| Dropdown | ✓ | ✓ | ✓ | |
| Checkbox | ✓ | ✓ | ✓ | |
| Radio | ✓ | ✓ | ✓ | |
| Toggle | ✓ | ✓ | ✓ | |
| Stepper | ✓ | ✓ | ✓ | |
| Progress bar | ✓ | ✓ | ✓ | |
| Feedback control | ✓ | ✓ | ✓ | |
| Action row | ✓ | ✓ | ✓ | |
| Tabs | ✓ | ✓ | ✓ | |
| Breadcrumb | ✓ | ✓ | ✓ | |
| Popover | ✓ | ✓ | ✓ | |
| Toast | ✓ | ✓ | ✓ | |
| Banner | ✓ | ✓ | ✓ | |
| Nudge card | ✓ | ✓ | ✓ | |
| Selection card | ✓ | ✓ | ✓ | |
| To-do list | ✓ | ✓ | Partial | |
| Chat message | ✓ | ✓ | ✓ | |
| Composer | ✓ | ✓ | ✓ | |
| Approval card | ✓ | ✓ | ✓ | |
| Code block | ✓ | ✓ | ✓ | |
| Inline diff | ✓ | ✓ | ✓ | |
| Agent activity | ✓ | ✓ | ✓ | |
| Conversation state machine | ✓ | ✓ | Partial | |
| Modal / Dialog | ✓ | ✓ | ✓ | |
| Bottom sheet | ✓ | ✓ | ✓ | |
| List row | ✓ | ✓ | ✓ | |
| Data table | ✓ | ✓ | ✓ | |
| Card | ✓ | ✓ | ✓ | |
| Form | ✓ | ✓ | ✓ | |
| Change-log feed | ✓ | ✓ | Partial |
The Figma library mirrors the atomic IA. Tokens published as Figma variables from tokens.json in DTCG 2025.10 format.
{
"$themes": [
{
"id": "light",
"name": "Light",
"selectedTokenSets": {
"global": "enabled",
"primitives": "source",
"semantic-light": "enabled",
"additions-light": "enabled"
}
},
{
"id": "dark",
"name": "Dark",
"selectedTokenSets": {
"global": "enabled",
"primitives": "source",
"semantic-dark": "enabled",
"additions-dark": "enabled"
}
}
],
"$metadata": {
"tokenSetOrder": [
"primitives",
"semantic-light",
"semantic-dark",
"additions-light",
"additions-dark",
"global"
]
},
"primitives": {
"slate": {
"25": {
"$value": "#FBFCFD",
"$type": "color"
},
"50": {
"$value": "#F6F8FA",
"$type": "color"
},
"100": {
"$value": "#EEF1F4",
"$type": "color"
},
"200": {
"$value": "#E2E6EB",
"$type": "color"
},
"300": {
"$value": "#C9D0D9",
"$type": "color"
},
"400": {
"$value": "#9AA4B2",
"$type": "color"
},
"500": {
"$value": "#6B7785",
"$type": "color"
},
"600": {
"$value": "#4D5867",
"$type": "color"
},
"700": {
"$value": "#364152",
"$type": "color"
},
"800": {
"$value": "#1F2937",
"$type": "color"
},
"900": {
"$value": "#111827",
"$type": "color"
},
"950": {
"$value": "#0A0F1A",
"$type": "color"
}
},
"indigo": {
"50": {
"$value": "#EEF0FF",
"$type": "color"
},
"100": {
"$value": "#DDE0FF",
"$type": "color"
},
"200": {
"$value": "#BAC1FF",
"$type": "color"
},
"300": {
"$value": "#8E97FF",
"$type": "color"
},
"400": {
"$value": "#6268FF",
"$type": "color"
},
"500": {
"$value": "#4A50EF",
"$type": "color"
},
"600": {
"$value": "#3438C9",
"$type": "color"
},
"700": {
"$value": "#252AA0",
"$type": "color"
},
"800": {
"$value": "#1B2078",
"$type": "color"
},
"900": {
"$value": "#121757",
"$type": "color"
}
},
"violet": {
"50": {
"$value": "#F4EEFF",
"$type": "color"
},
"100": {
"$value": "#E6DAFF",
"$type": "color"
},
"200": {
"$value": "#CDB5FF",
"$type": "color"
},
"300": {
"$value": "#B08CFF",
"$type": "color"
},
"400": {
"$value": "#8E61F2",
"$type": "color"
},
"500": {
"$value": "#6E40D9",
"$type": "color"
},
"600": {
"$value": "#5A2FB8",
"$type": "color"
},
"700": {
"$value": "#46248F",
"$type": "color"
}
},
"sky": {
"50": {
"$value": "#E6F1FF",
"$type": "color"
},
"100": {
"$value": "#C7E1FF",
"$type": "color"
},
"200": {
"$value": "#8EC1FF",
"$type": "color"
},
"300": {
"$value": "#5EA8FF",
"$type": "color"
},
"400": {
"$value": "#3BA7FF",
"$type": "color"
},
"500": {
"$value": "#1564CC",
"$type": "color"
},
"600": {
"$value": "#114DA0",
"$type": "color"
},
"700": {
"$value": "#0D3C7E",
"$type": "color"
}
},
"green": {
"50": {
"$value": "#E7F6EC",
"$type": "color"
},
"100": {
"$value": "#C6EAD2",
"$type": "color"
},
"200": {
"$value": "#8FD7A6",
"$type": "color"
},
"300": {
"$value": "#4FBE76",
"$type": "color"
},
"400": {
"$value": "#2A9D55",
"$type": "color"
},
"500": {
"$value": "#1F7A42",
"$type": "color"
},
"600": {
"$value": "#185C32",
"$type": "color"
},
"700": {
"$value": "#103E22",
"$type": "color"
}
},
"amber": {
"50": {
"$value": "#FFF6E0",
"$type": "color"
},
"100": {
"$value": "#FFE5A8",
"$type": "color"
},
"200": {
"$value": "#FFCB60",
"$type": "color"
},
"300": {
"$value": "#E89B1A",
"$type": "color"
},
"400": {
"$value": "#B97300",
"$type": "color"
},
"500": {
"$value": "#8A5500",
"$type": "color"
},
"600": {
"$value": "#5C3800",
"$type": "color"
}
},
"red": {
"50": {
"$value": "#FFECEC",
"$type": "color"
},
"100": {
"$value": "#FFCFCF",
"$type": "color"
},
"200": {
"$value": "#FF9C9C",
"$type": "color"
},
"300": {
"$value": "#F55959",
"$type": "color"
},
"400": {
"$value": "#D92D2D",
"$type": "color"
},
"500": {
"$value": "#B11E1E",
"$type": "color"
},
"600": {
"$value": "#8A1414",
"$type": "color"
}
}
},
"semantic-light": {
"bg": {
"canvas": {
"$value": "{primitives.slate.50}",
"$type": "color"
},
"surface": {
"$value": "#FFFFFF",
"$type": "color"
},
"surfaceAlt": {
"$value": "{primitives.slate.25}",
"$type": "color"
},
"sunken": {
"$value": "{primitives.slate.100}",
"$type": "color"
},
"brand": {
"$value": "{primitives.indigo.600}",
"$type": "color"
},
"brandSubtle": {
"$value": "{primitives.indigo.50}",
"$type": "color"
},
"selected": {
"$value": "{primitives.indigo.50}",
"$type": "color"
}
},
"text": {
"primary": {
"$value": "{primitives.slate.900}",
"$type": "color"
},
"secondary": {
"$value": "{primitives.slate.700}",
"$type": "color"
},
"tertiary": {
"$value": "{primitives.slate.500}",
"$type": "color"
},
"onBrand": {
"$value": "#FFFFFF",
"$type": "color"
},
"link": {
"$value": "{primitives.indigo.600}",
"$type": "color"
}
},
"border": {
"subtle": {
"$value": "{primitives.slate.100}",
"$type": "color"
},
"default": {
"$value": "{primitives.slate.200}",
"$type": "color"
},
"strong": {
"$value": "{primitives.slate.300}",
"$type": "color"
},
"focus": {
"$value": "{primitives.indigo.600}",
"$type": "color"
},
"danger": {
"$value": "{primitives.red.400}",
"$type": "color"
}
}
},
"semantic-dark": {
"bg": {
"canvas": {
"$value": "#0E1424",
"$type": "color"
},
"surface": {
"$value": "#131B2C",
"$type": "color"
},
"surfaceAlt": {
"$value": "#182135",
"$type": "color"
},
"sunken": {
"$value": "#0A101C",
"$type": "color"
},
"brand": {
"$value": "{primitives.indigo.400}",
"$type": "color"
},
"brandSubtle": {
"$value": "#1F2A55",
"$type": "color"
},
"selected": {
"$value": "#1F2A55",
"$type": "color"
}
},
"text": {
"primary": {
"$value": "#F4F6FA",
"$type": "color"
},
"secondary": {
"$value": "#C8CFDB",
"$type": "color"
},
"tertiary": {
"$value": "#94A0B4",
"$type": "color"
},
"onBrand": {
"$value": "#0A0F1A",
"$type": "color"
},
"link": {
"$value": "{primitives.indigo.300}",
"$type": "color"
}
},
"border": {
"subtle": {
"$value": "#1F2940",
"$type": "color"
},
"default": {
"$value": "#2A3654",
"$type": "color"
},
"strong": {
"$value": "#3A4870",
"$type": "color"
},
"focus": {
"$value": "{primitives.indigo.300}",
"$type": "color"
},
"danger": {
"$value": "{primitives.red.300}",
"$type": "color"
}
}
},
"global": {
"type": {
"displayLg": {
"$value": {
"fontFamily": "Roboto",
"fontSize": "40px",
"lineHeight": "48px",
"fontWeight": "600",
"letterSpacing": "-0.02em"
},
"$type": "typography"
},
"displayMd": {
"$value": {
"fontFamily": "Roboto",
"fontSize": "32px",
"lineHeight": "40px",
"fontWeight": "600",
"letterSpacing": "-0.015em"
},
"$type": "typography"
},
"h1": {
"$value": {
"fontFamily": "Roboto",
"fontSize": "28px",
"lineHeight": "36px",
"fontWeight": "600",
"letterSpacing": "-0.01em"
},
"$type": "typography"
},
"h2": {
"$value": {
"fontFamily": "Roboto",
"fontSize": "22px",
"lineHeight": "30px",
"fontWeight": "600",
"letterSpacing": "-0.005em"
},
"$type": "typography"
},
"h3": {
"$value": {
"fontFamily": "Roboto",
"fontSize": "18px",
"lineHeight": "26px",
"fontWeight": "600"
},
"$type": "typography"
},
"h4": {
"$value": {
"fontFamily": "Roboto",
"fontSize": "16px",
"lineHeight": "24px",
"fontWeight": "600"
},
"$type": "typography"
},
"bodyLg": {
"$value": {
"fontFamily": "Roboto",
"fontSize": "16px",
"lineHeight": "26px",
"fontWeight": "400"
},
"$type": "typography"
},
"bodyMd": {
"$value": {
"fontFamily": "Roboto",
"fontSize": "14px",
"lineHeight": "22px",
"fontWeight": "400"
},
"$type": "typography"
},
"bodySm": {
"$value": {
"fontFamily": "Roboto",
"fontSize": "13px",
"lineHeight": "20px",
"fontWeight": "400"
},
"$type": "typography"
},
"labelLg": {
"$value": {
"fontFamily": "Roboto",
"fontSize": "15px",
"lineHeight": "22px",
"fontWeight": "500"
},
"$type": "typography"
},
"labelMd": {
"$value": {
"fontFamily": "Roboto",
"fontSize": "14px",
"lineHeight": "20px",
"fontWeight": "500"
},
"$type": "typography"
},
"labelSm": {
"$value": {
"fontFamily": "Roboto",
"fontSize": "12px",
"lineHeight": "16px",
"fontWeight": "500"
},
"$type": "typography"
},
"caption": {
"$value": {
"fontFamily": "Roboto",
"fontSize": "12px",
"lineHeight": "16px",
"fontWeight": "400"
},
"$type": "typography"
},
"overline": {
"$value": {
"fontFamily": "Roboto",
"fontSize": "11px",
"lineHeight": "16px",
"fontWeight": "500",
"letterSpacing": "0.08em"
},
"$type": "typography"
},
"code": {
"$value": {
"fontFamily": "SF Mono",
"fontSize": "13px",
"lineHeight": "20px",
"fontWeight": "400"
},
"$type": "typography"
}
},
"spacing": {
"0": {
"$value": "0px",
"$type": "dimension"
},
"1": {
"$value": "4px",
"$type": "dimension"
},
"2": {
"$value": "8px",
"$type": "dimension"
},
"3": {
"$value": "12px",
"$type": "dimension"
},
"4": {
"$value": "16px",
"$type": "dimension"
},
"5": {
"$value": "20px",
"$type": "dimension"
},
"6": {
"$value": "24px",
"$type": "dimension"
},
"8": {
"$value": "32px",
"$type": "dimension"
},
"10": {
"$value": "40px",
"$type": "dimension"
},
"12": {
"$value": "48px",
"$type": "dimension"
},
"16": {
"$value": "64px",
"$type": "dimension"
},
"20": {
"$value": "80px",
"$type": "dimension"
}
},
"radius": {
"xs": {
"$value": "4px",
"$type": "dimension"
},
"sm": {
"$value": "6px",
"$type": "dimension"
},
"md": {
"$value": "8px",
"$type": "dimension"
},
"lg": {
"$value": "12px",
"$type": "dimension"
},
"xl": {
"$value": "16px",
"$type": "dimension"
},
"2xl": {
"$value": "20px",
"$type": "dimension"
},
"pill": {
"$value": "999px",
"$type": "dimension"
},
"circle": {
"$value": "9999px",
"$type": "dimension"
}
},
"elevation": {
"xs": {
"$value": {
"color": "rgba(17,24,39,0.06)",
"offsetX": "0px",
"offsetY": "1px",
"blur": "2px",
"spread": "0px"
},
"$type": "shadow"
},
"sm": {
"$value": {
"color": "rgba(17,24,39,0.08)",
"offsetX": "0px",
"offsetY": "1px",
"blur": "3px",
"spread": "0px"
},
"$type": "shadow"
},
"md": {
"$value": {
"color": "rgba(17,24,39,0.10)",
"offsetX": "0px",
"offsetY": "4px",
"blur": "8px",
"spread": "-2px"
},
"$type": "shadow"
},
"lg": {
"$value": {
"color": "rgba(17,24,39,0.14)",
"offsetX": "0px",
"offsetY": "12px",
"blur": "24px",
"spread": "-6px"
},
"$type": "shadow"
},
"focus": {
"$value": {
"color": "rgba(52,56,201,0.32)",
"offsetX": "0px",
"offsetY": "0px",
"blur": "0px",
"spread": "3px"
},
"$type": "shadow"
}
},
"motion": {
"fast": {
"$value": "120ms cubic-bezier(0.4,0,0.2,1)",
"$type": "duration"
},
"base": {
"$value": "200ms cubic-bezier(0.4,0,0.2,1)",
"$type": "duration"
},
"slow": {
"$value": "320ms cubic-bezier(0.4,0,0.2,1)",
"$type": "duration"
}
}
},
"additions-light": {
"ai": {
"accent": {
"$value": "{primitives.violet.600}",
"$type": "color"
},
"accentSurface": {
"$value": "{primitives.violet.50}",
"$type": "color"
},
"accentBorder": {
"$value": "{primitives.violet.200}",
"$type": "color"
},
"onAccent": {
"$value": "#FFFFFF",
"$type": "color"
}
},
"citation": {
"bg": {
"$value": "{primitives.slate.50}",
"$type": "color"
},
"bgHover": {
"$value": "{primitives.slate.100}",
"$type": "color"
},
"border": {
"$value": "{primitives.slate.200}",
"$type": "color"
},
"text": {
"$value": "{primitives.slate.700}",
"$type": "color"
},
"icon": {
"$value": "{primitives.slate.500}",
"$type": "color"
}
},
"diff": {
"addBg": {
"$value": "#E7F6EC",
"$type": "color"
},
"addFg": {
"$value": "{primitives.green.600}",
"$type": "color"
},
"addMarker": {
"$value": "{primitives.green.400}",
"$type": "color"
},
"removeBg": {
"$value": "#FFECEC",
"$type": "color"
},
"removeFg": {
"$value": "{primitives.red.500}",
"$type": "color"
},
"removeMarker": {
"$value": "{primitives.red.400}",
"$type": "color"
},
"contextBg": {
"$value": "#FFFFFF",
"$type": "color"
},
"contextFg": {
"$value": "{primitives.slate.700}",
"$type": "color"
},
"gutterBg": {
"$value": "{primitives.slate.50}",
"$type": "color"
},
"gutterFg": {
"$value": "{primitives.slate.500}",
"$type": "color"
}
},
"code": {
"bg": {
"$value": "{primitives.slate.50}",
"$type": "color"
},
"bgInline": {
"$value": "{primitives.slate.100}",
"$type": "color"
},
"border": {
"$value": "{primitives.slate.200}",
"$type": "color"
},
"fg": {
"$value": "{primitives.slate.900}",
"$type": "color"
}
},
"syntax": {
"keyword": {
"$value": "{primitives.indigo.700}",
"$type": "color"
},
"string": {
"$value": "{primitives.green.600}",
"$type": "color"
},
"number": {
"$value": "{primitives.amber.500}",
"$type": "color"
},
"comment": {
"$value": "{primitives.slate.500}",
"$type": "color"
},
"operator": {
"$value": "{primitives.slate.700}",
"$type": "color"
},
"function": {
"$value": "{primitives.violet.600}",
"$type": "color"
},
"punctuation": {
"$value": "{primitives.slate.600}",
"$type": "color"
}
},
"approval": {
"draftBg": {
"$value": "{primitives.slate.100}",
"$type": "color"
},
"draftText": {
"$value": "{primitives.slate.700}",
"$type": "color"
},
"awaitingBg": {
"$value": "{primitives.amber.50}",
"$type": "color"
},
"awaitingText": {
"$value": "{primitives.amber.500}",
"$type": "color"
},
"approvedBg": {
"$value": "{primitives.green.50}",
"$type": "color"
},
"approvedText": {
"$value": "{primitives.green.600}",
"$type": "color"
},
"liveBg": {
"$value": "{primitives.indigo.50}",
"$type": "color"
},
"liveText": {
"$value": "{primitives.indigo.700}",
"$type": "color"
},
"rejectedBg": {
"$value": "{primitives.red.50}",
"$type": "color"
},
"rejectedText": {
"$value": "{primitives.red.500}",
"$type": "color"
}
}
},
"additions-dark": {
"ai": {
"accent": {
"$value": "{primitives.violet.200}",
"$type": "color"
},
"accentSurface": {
"$value": "#2A1A4D",
"$type": "color"
},
"accentBorder": {
"$value": "{primitives.violet.400}",
"$type": "color"
},
"onAccent": {
"$value": "#0A0F1A",
"$type": "color"
}
},
"citation": {
"bg": {
"$value": "#182135",
"$type": "color"
},
"bgHover": {
"$value": "#1C2640",
"$type": "color"
},
"border": {
"$value": "#2A3654",
"$type": "color"
},
"text": {
"$value": "#C8CFDB",
"$type": "color"
},
"icon": {
"$value": "{primitives.slate.400}",
"$type": "color"
}
},
"diff": {
"addBg": {
"$value": "#0E2E1B",
"$type": "color"
},
"addFg": {
"$value": "#6FD995",
"$type": "color"
},
"addMarker": {
"$value": "{primitives.green.300}",
"$type": "color"
},
"removeBg": {
"$value": "#2E1010",
"$type": "color"
},
"removeFg": {
"$value": "#F58B8B",
"$type": "color"
},
"removeMarker": {
"$value": "{primitives.red.300}",
"$type": "color"
},
"contextBg": {
"$value": "#131B2C",
"$type": "color"
},
"contextFg": {
"$value": "#C8CFDB",
"$type": "color"
},
"gutterBg": {
"$value": "#0A101C",
"$type": "color"
},
"gutterFg": {
"$value": "{primitives.slate.400}",
"$type": "color"
}
},
"code": {
"bg": {
"$value": "#0A101C",
"$type": "color"
},
"bgInline": {
"$value": "#182135",
"$type": "color"
},
"border": {
"$value": "#2A3654",
"$type": "color"
},
"fg": {
"$value": "#F4F6FA",
"$type": "color"
}
},
"syntax": {
"keyword": {
"$value": "{primitives.indigo.200}",
"$type": "color"
},
"string": {
"$value": "#6FD995",
"$type": "color"
},
"number": {
"$value": "#F0B95B",
"$type": "color"
},
"comment": {
"$value": "{primitives.slate.400}",
"$type": "color"
},
"operator": {
"$value": "#C8CFDB",
"$type": "color"
},
"function": {
"$value": "{primitives.violet.200}",
"$type": "color"
},
"punctuation": {
"$value": "{primitives.slate.300}",
"$type": "color"
}
},
"approval": {
"draftBg": {
"$value": "#1C2640",
"$type": "color"
},
"draftText": {
"$value": "#C8CFDB",
"$type": "color"
},
"awaitingBg": {
"$value": "#2E2310",
"$type": "color"
},
"awaitingText": {
"$value": "#F0B95B",
"$type": "color"
},
"approvedBg": {
"$value": "#0E2E1B",
"$type": "color"
},
"approvedText": {
"$value": "#6FD995",
"$type": "color"
},
"liveBg": {
"$value": "#1F2A55",
"$type": "color"
},
"liveText": {
"$value": "#BCC4FF",
"$type": "color"
},
"rejectedBg": {
"$value": "#2E1010",
"$type": "color"
},
"rejectedText": {
"$value": "#F58B8B",
"$type": "color"
}
}
},
"$schema": "https://design-tokens.github.io/community-group/format/2025-10/",
"$description": "aiRA Design System First Edition \u2014 DTCG 2025.10 format. Agent identity tokens removed (rule: one aiRA)."
}
This file is the source of truth a Figma designer uses to draw v2 component frames. Component names below match CSS selectors in ../index.html 1:1, so Figma → code stays in sync.
How to use 1. Import tokens.json via the Tokens Studio plugin → tokens become Figma variables. 2. For each component below, create a Figma Component with the listed variants and properties. 3. Name the Figma component exactly as the Component name field — this matches code-connect.json. 4. Reference the live HTML demo in ../index.html for visual sizing.
All measurements in px. All colors reference Tokens Studio variables (e.g. ai.accent).
---
---
Component name: chat/Bubble Selector: .msg
Variants (Figma variant property):
role: user · ai · system · erroragent (ai only): config · qa · analytics · support · coderstate: default · hover · selecteddensity: comfortable · compactProperties (Figma component properties):
Text → message body (instance swap, supports markdown)Show meta → boolean (default true for ai/error)Show actions → booleanTimestamp → textAnatomy (top → bottom): 1. Meta row (ai only) — Auto-layout horizontal, 8px gap, 4px bottom padding. - AgentBadge slot · AILabel · Timestamp (caption, tertiary) 2. Body — Max width 80%, padding 12/16, radius lg (12), one corner pinched to xs (4). - user: bg brand, text onBrand, right-pinch - ai: bg surface, text primary, border default, left-pinch - system: bg statusNeutral, text secondary, full radius md - error: bg state.aiError, border state.aiError, text state.aiError 3. Action row (optional) — 8px top gap, horizontal auto-layout. Copy · Support · Feedback group.
States | State | Treatment | |---|---| | default | base | | hover | actions reveal (opacity 0 → 1, 120ms) | | focus-visible | 2px ring border.focus, offset 2 | | streaming (ai) | trailing pulse dot, 1.2s ease loop |
A11y: list wrapper has aria-live="polite". Action buttons have aria-label. AI label is text + dot, never color only.
---
Component name: ai/Label Selector: .ai-label
Variants:
size: inline (pill, 11/16 overline) · block (label, 12/16, full disclosure copy)Anatomy:
ai.accent, 6px gap.ai.accent on ai.accentSurface, border ai.accentBorder.pill for inline, md for block.Mandatory use: on every AI-authored output (chat replies, generated configs, generated SQL, drafted copy).
---
Component name: approval/Card Selector: .approval
Variants:
status: draft · awaiting · approved · live · rejectedrisk: standard · destructive (drives primary button variant)Properties:
Title (header text)Agent (AgentBadge swap)Preview slot (instance swap: Diff, Table, Chart, RichText)Anatomy (vertical auto-layout, radius lg, border ai.accentBorder): 1. Header — Padding 16/20, bg ai.accentSurface, border-bottom subtle. - AgentBadge · AILabel ("Proposal") · Title (h4, primary) 2. Body — Padding 16/20. - Framing line (body-md, secondary) - Preview slot 3. Footer — Padding 12/20, bg surfaceAlt, border-top subtle. - Status pill (left) · Spacer · Reject (tertiary) · Edit (secondary) · Accept (primary, right)
States | Status | Pill | Primary action | |---|---|---| | draft | Draft (slate) | Submit for review | | awaiting | Awaiting approval (amber) | Approve / Reject | | approved | Approved (green) | Make live | | live | Live (indigo) | (read-only) | | rejected | Rejected (red) | Revert to draft |
A11y: wrap as <article aria-labelledby>. For destructive risk, chain a Modal confirmation.
---
Component name: citation/Chip Selector: .citation
Variants:
mode: inline · listedstate: default · hover · focusProperties:
Number (overline)Title (label-sm)Show snippet on hover (boolean — pairs with Popover)Anatomy:
sm.citation.bg, text citation.text, border citation.border.Sources block under the answer.A11y: real anchor (<a href>) — never div onclick. Hover popover surfaces the cited snippet.
---
Component name: code/Block Selector: .code-block
Variants:
lang: sql · javascript · python · yaml · textstate: default · collapsedai-labelled: true · falseProperties:
Code (text, monospace)Lang (badge text)Show Copy / Explain (boolean)Anatomy:
md, border code.border, bg code.bg.surfaceAlt, border-bottom. Lang overline (tertiary) · AILabel (optional) · spacer · Copy · Explain (tertiary buttons).code, color code.fg. Syntax token colors per v2-additions..syntax..Collapsed: cap at 240px height with a "Show all" tertiary button below.
---
Component name: diff/Inline Selector: .diff
Variants:
density: comfortable · compactshow-line-numbers: true · falseAnatomy (per line: 36px gutter + 1fr body):
diff.gutterBg, fg diff.gutterFg, font overline, right-aligned.diff.addBg, fg diff.addFg, gutter prefixed with +.diff.removeBg, fg diff.removeFg, gutter prefixed with −.diff.contextBg, fg diff.contextFg.Always pair sign + color — the + / − marker is the primary signal.
---
Component name: activity/Row and activity/List Selector: .activity, .activity-list
Variants (Row):
state: thinking · streaming · stopped · errorVariants (List item):
state: pending · active · done · failedAnatomy (Row):
md, bg state.aiThinking, border state.aiThinking, text state.aiThinking.Anatomy (List):
A11y: wrap in role="status" aria-live="polite". Honor prefers-reduced-motion (spinner stops, surface stays).
---
Component name: feedback/Control Selector: .feedback
Variants:
state: default · up-selected · down-selected · disabledsize: sm (24) · md (28) · lg (32)Anatomy:
sm, border default.statusSuccess, border green.300.statusDanger, border red.300.A11y: role="group" + aria-label="Was this helpful?". Selected button uses aria-pressed="true". Down selection opens a reason picker Popover.
---
Component name: overlay/Modal Selector: .modal
Variants:
intent: confirmation · destructive · info · formsize: sm (440) · md (560) · lg (720)Properties:
Title (h3, primary)Body slot (instance swap)Primary CTA (button)Secondary CTA (button)Anatomy:
xl (16). Shadow lg.subtle.surfaceAlt, border-top, actions right-aligned with 8px gap.Scrim: bg.overlay (rgba slate-900 .48 light / black .60 dark).
A11y: role="dialog" aria-modal="true" aria-labelledby. Focus trap. Esc closes. Focus returns to trigger.
---
Component name: overlay/BottomSheet Selector: .sheet
Variants:
size: peek (25%) · default (50%) · full (95%)dismissible: true · false (drag handle visibility & scrim click)Anatomy:
lg. Bg surface.20 + safe.bottom.border.strong, centered, 12px below the top edge.A11y: same as Modal. Handle is decorative — always include explicit Cancel / Apply buttons.
---
---
Component name: nav/Tabs Selector: .tabs
Variants: size: sm · md. style: line · contained. Anatomy: horizontal auto-layout, border-bottom default. Each tab: padding 12/12, 2px bottom border (transparent → border.focus when selected). Selected: text link. A11y: role="tablist", items role="tab" with aria-selected. Arrow-key nav.
---
Component name: form/Select Selector: .field select
Variants: state: default · hover · focus · open · filled · disabled · error. Anatomy: 36px height, padding 8/12 (right 32 for chevron). Bg surface, border default, radius md. Chevron: two 5px slate-tertiary stripes.
---
Component name: form/Combobox Selector: .combo
Variants: state: closed · open · loading · empty. Anatomy:
surface, border default, radius md, shadow md, max-height 240, scrollable.hover, selected bg selected. Optional sub-label (caption, right-aligned, tertiary).A11y: role="combobox" + aria-expanded + aria-controls. Menu role="listbox". Items role="option" + aria-selected.
---
Component name: form/Toggle Selector: .toggle
Variants: state: off · on · disabled · focused. Anatomy: Track 36×20, radius 999. Thumb 16×16, white, shadow xs. Off: bg slate.300. On: bg brand. Translate 16 on toggle, 200ms ease.
---
Component name: form/Checkbox Selector: .opt input[type=checkbox]
Variants: state: unchecked · checked · indeterminate · disabled · error. Anatomy: 18×18, radius xs. Unchecked: border default. Checked: bg brand, white tick. Indeterminate: bg brand, white horizontal bar.
---
Component name: form/RadioGroup Selector: fieldset + .opt input[type=radio]
Variants (per option): state: unchecked · checked · disabled. Anatomy: 18×18 circle, radius 50%. Checked: 6px inner dot in brand. A11y: wrap in <fieldset> with <legend>. Arrow keys move between options.
---
Component name: overlay/Tooltip, overlay/Popover Selector: .tip, .popover
Tooltip:
slate.900 (light) / slate.100 (dark). Text white/slate-900. Padding 6/8. Radius sm. Shadow md. Max-width 220.Popover:
surface, border default, radius md, shadow md, padding 12/16. Max-width 320.role="dialog").---
Component name: avatar/Avatar Selector: .avatar
Variants:
size: sm (24) · md (32) · lg (44)kind: user · ai · agent.config · agent.qa · agent.analytics · agent.support · agent.coderAnatomy: circle, font label-md (sm: label-sm, lg: h4). user: bg brand, text onBrand. ai: gradient aira-gradient. agent.: bg matching agent. token.
---
Component name: list/Row Selector: .list-row
Variants:
density: comfortable (min-height 44) · dense (min-height 36)state: default · hover · selected · disabledAnatomy: 3-column grid (auto · 1fr · auto), 12px gap, padding 12/16. Border-bottom subtle.
A11y: entire row is one interactive surface (<a> or <button>); never split.
---
Component name: data/Table Selector: .dt
Variants (cell): align: start · end. type: text · number · delta. Anatomy:
surfaceAlt, label-sm, text primary.tableDivider. Hover bg hover.delta.positive (green) with +; negative uses delta.negative (orange, NOT red) with −.---
Component names: progress/Stepper, progress/Bar Selectors: .stepper, .progress
Stepper variants (per step): state: pending · active · done · failed. Anatomy:
Progress bar:
sunken. Fill brand, animated to width.role="progressbar" + aria-valuemin/max/now.---
Skeleton — loading/Skeleton. Selector .skeleton. Bg = animated gradient sunken → hover → sunken, 1.4s loop. aria-hidden="true". Variants: line · circle · block · bubble.
Form anatomy — composition pattern, not a component. Wrap fields in .field; label connected via for/id; helper text (caption) below; error text (caption, danger) replaces helper on aria-invalid="true"; required marker = visible asterisk + required attribute.
---
All Figma components are named category/Name (Pascal-cased name), e.g. chat/Bubble, overlay/Modal. The slash creates Figma's nested-component menu structure. The code-connect.json file pairs each name to its file path so Figma's Dev Mode can jump to source.
{
"$schema": "https://schemas.figma.com/code-connect/v1/code-connect.schema.json",
"version": "1.0.0",
"designSystem": "aiRA Design System v2",
"sourceRoot": "../",
"components": [
{
"figmaName": "chat/Bubble",
"source": "index.html#chat-bubble",
"selector": ".msg",
"variants": ["role", "agent", "state", "density"],
"props": ["text", "showMeta", "showActions", "timestamp"]
},
{
"figmaName": "ai/Label",
"source": "index.html#ai-label",
"selector": ".ai-label",
"variants": ["size"]
},
{
"figmaName": "approval/Card",
"source": "index.html#approval-card",
"selector": ".approval",
"variants": ["status", "risk"],
"props": ["title", "agent", "previewSlot"]
},
{
"figmaName": "citation/Chip",
"source": "index.html#citation",
"selector": ".citation",
"variants": ["mode", "state"],
"props": ["number", "title", "showSnippetOnHover"]
},
{
"figmaName": "code/Block",
"source": "index.html#code-block",
"selector": ".code-block",
"variants": ["lang", "state", "aiLabelled"],
"props": ["code", "lang", "showCopyExplain"]
},
{
"figmaName": "diff/Inline",
"source": "index.html#diff",
"selector": ".diff",
"variants": ["density", "showLineNumbers"]
},
{
"figmaName": "activity/Row",
"source": "index.html#activity",
"selector": ".activity",
"variants": ["state"]
},
{
"figmaName": "activity/List",
"source": "index.html#activity",
"selector": ".activity-list",
"variants": ["itemState"]
},
{
"figmaName": "feedback/Control",
"source": "index.html#feedback",
"selector": ".feedback",
"variants": ["state", "size"]
},
{
"figmaName": "overlay/Modal",
"source": "index.html#modal",
"selector": ".modal",
"variants": ["intent", "size"],
"props": ["title", "bodySlot", "primaryCta", "secondaryCta"]
},
{
"figmaName": "overlay/BottomSheet",
"source": "index.html#bottom-sheet",
"selector": ".sheet",
"variants": ["size", "dismissible"]
},
{
"figmaName": "nav/Tabs",
"source": "index.html#tabs",
"selector": ".tabs",
"variants": ["size", "style"]
},
{
"figmaName": "form/Select",
"source": "index.html#select",
"selector": ".field select",
"variants": ["state"]
},
{
"figmaName": "form/Combobox",
"source": "index.html#combobox",
"selector": ".combo",
"variants": ["state"]
},
{
"figmaName": "form/Toggle",
"source": "index.html#toggle",
"selector": ".toggle",
"variants": ["state"]
},
{
"figmaName": "form/Checkbox",
"source": "index.html#checkbox",
"selector": ".opt input[type=checkbox]",
"variants": ["state"]
},
{
"figmaName": "form/RadioGroup",
"source": "index.html#radio",
"selector": "fieldset",
"variants": ["optionState"]
},
{
"figmaName": "overlay/Tooltip",
"source": "index.html#tooltip",
"selector": ".tip"
},
{
"figmaName": "overlay/Popover",
"source": "index.html#tooltip",
"selector": ".popover"
},
{
"figmaName": "avatar/Avatar",
"source": "index.html#avatar",
"selector": ".avatar",
"variants": ["size", "kind"]
},
{
"figmaName": "list/Row",
"source": "index.html#list-row",
"selector": ".list-row",
"variants": ["density", "state"]
},
{
"figmaName": "data/Table",
"source": "index.html#data-table",
"selector": ".dt",
"variants": ["cellAlign", "cellType"]
},
{
"figmaName": "progress/Stepper",
"source": "index.html#stepper",
"selector": ".stepper",
"variants": ["stepState"]
},
{
"figmaName": "progress/Bar",
"source": "index.html#stepper",
"selector": ".progress"
},
{
"figmaName": "loading/Skeleton",
"source": "index.html#skeleton",
"selector": ".skeleton",
"variants": ["shape"]
}
],
"tokens": {
"format": "tokens-studio",
"source": "tokens.json",
"css": "../tokens.css"
},
"notes": [
"Import tokens.json via the Tokens Studio Figma plugin to materialize variables/styles.",
"Each figmaName above must be created as a Figma Component with the listed variants.",
"After creating the components, run Figma → Plugins → Code Connect → Sync to bind them to this file.",
"The figmaName slashes ('chat/Bubble') create Figma's nested-component menu structure."
]
}
Work consciously left out of the First Edition. Each item has a trigger condition for re-opening.
| Item | Why deferred | Re-open when |
|---|---|---|
| React component library | Building it now would lock the system to a framework before the patterns are stable. | Patterns hold for two consecutive product releases without breaking changes. |
| Cmd+K command palette + slash commands | The command surface isn't designed yet (which commands, namespaced how, scoped to which agent). | Product defines the command vocabulary. |
| Multi-brand theming hooks | The reseller/white-label contract isn't agreed yet. See Approval card · Multi-brand override path for the shape. | First reseller deployment commits. |
| State-layer system (opacity-based hover/pressed) | Token cost and complexity outweighs the benefit at the current system scale. | System grows past ~80 components. |
| Density modes (compact / comfortable / spacious) | Two density requirements would force every component to re-spec. Premature. | A product asks for compact view in writing. |
| Responsive breakpoint tokens | The First Edition surfaces are desktop-first; mobile is a single break. | Tablet or kiosk surface ships. |
| Style Dictionary export (JS / Swift / Kotlin) | Only the web app consumes tokens today. | Native client (iOS or Android) commits to the system. |
| i18n & bidi foundation pages | The earlier standalone pages were dropped; the rule lives in tokens.css §7a but isn't documented. | Next edition. |