Guidelines

Brand book

Iris UI is a quiet, precise interface system for AI-native products: cool neutrals, hairlines and smooth shadows, with motion-first components and page blocks on one token set. One blue accent, one ink, tight radii, Inter for the interface and Geist for display.

Principles

  • Neutral first, colour as a condiment. Surfaces and text come from the cool, blue-tinted neutral ramp. accent marks selection, focus and the one primary action per view; green, orange, red appear only as status, always beside a word.
  • Hairlines, not boxes. Edges are 1px solid line (never alpha), or a ring baked into the shadow (shadow-card, shadow-btn). Don't add a border to something that already carries a ring shadow.
  • Live, never a picture. Show real working components with realistic data; no screenshots of UI, no stock illustrations.
  • Motion explains state. Things morph, roll and slide to show what changed, and every animation has a reduced-motion path.

Content fundamentals

  • Sentence case everywhere: buttons, titles, menu items, table headers (“Start free trial”, “Biggest movers”). No title case, no all-caps except micro tags.
  • Short and literal. Buttons are a verb plus object (“Delete project”, “Run tool”); confirmations say what happened (“Archived 12 files”) and offer the way back (“Undo”).
  • Address the reader as “you”; the product speaks as “we” only in legal and support copy. Agents speak in first person in traces (“I should check inventory before…”).
  • Numbers as digits with units: “412 articles”, “182 ms”, “+18.7%”. Use tabular numerals for anything that updates.
  • No emoji, no exclamation marks, no filler adjectives. Em dashes are fine in descriptions.
  • Errors say what to do: “Enter your email address”, “That email doesn't look right”.

Colour

Each swatch shows its light and dark value on that theme's surface. Text colours carry their contrast onsurface (on its own ground for on-accent and tooltip text).

Ground

light
dark
pageThe page behind everything: app background and landing-page body. Cool, faintly blue-tinted near-white; never pure white (that is surface).
light
dark
canvasRecessed working areas a step below page: chat canvas, gallery wells, the area behind a card grid. Text on it: ink or ink-2.
light
dark
surfaceCards, inputs, menus and anything that sits ON the page. Pair with shadow-card (light) for its hairline edge. ink reads 16:1 on it, ink-2 5.9:1.
light
dark
insetWells inside a surface: code blocks, table headers, the quiet strip under a card's body.
light
dark
hoverHover fill for rows, menu items and quiet buttons on surface.
light
dark
hover-2Stronger hover / pressed fill; the resting fill of ghost buttons.
light
dark
fieldFilled-field and pill-button resting background (prompt bars, the rail's “Book a call” pill).

Ink

light · 16.1:1
dark · 14.0:1
inkPrimary text and icons on page, canvas, surface, inset and hover (≥13:1 in both themes). Also the fill of the primary button and of an ON switch.
light · 5.8:1
dark · 6.5:1
ink-2Secondary text: descriptions, metadata, inactive tabs. 5.2–5.9:1 on page/canvas/surface in light, 6.5–7.5:1 in dark.
light · 2.7:1
dark · 3.0:1
ink-3Tertiary marks only: placeholders, disabled labels, timestamps beside stronger text, decorative glyphs. Source value kept exact: 2.7:1 on surface (light) / 3.1:1 (dark) — below 4.5:1, so never the only carrier of meaning.

Lines

light
dark
lineDefault hairline: card edges, dividers, table rules. Solid, never alpha. Decorative only (1.2:1) — controls that must be found use line-strong or a shadow ring.
light
dark
line-strongStronger hairline: the button ring (shadow-btn), toolbar rules, the OFF switch track.
light
dark
line-softSoftest rule: separators inside a surface, zebra edges.

Accent

light · 3.6:1
dark · 5.3:1
accentThe one blue accent: selection, focus outline, progress, links-as-actions, the accent button fill. 3.6:1 on surface (light) — fine for marks, rings and 19px+ bold text; for small text use accent-ink.
light · 4.8:1
dark · 8.0:1
accent-inkAccent as TEXT and the accent button's hover fill. 4.8:1 on surface light, 8:1 dark.
light
dark
accent-tintSelected-row and chip background, text selection. Text on it: accent-ink (4.3:1 light — use 13px medium or larger) or ink.
light · 3.6:1
dark · 6.1:1
on-accentText and glyphs ON an accent fill. White in light (3.6:1 — the source's choice; keep accent-filled labels 13px medium+), page in dark where the lighter accent wants dark text (5.4:1).

Status

light · 3.6:1
dark · 6.3:1
greenSuccess / done / additions. A condiment: icons, dots, the diff + count, always beside a word. 3.6:1 on surface in light.
light
dark
green-tintSuccess pill and added-line background; its text is green or ink.
light · 2.9:1
dark · 6.5:1
orangeWarning / pending / needs review. Marks and labels beside a word; 3:1 on surface in light, so not for small body text.
light
dark
orange-tintWarning pill background; its text is ink (orange text on it is 2.7:1 in light).
light · 3.9:1
dark · 4.6:1
redDanger / errors / deletions. Always with a word or icon. 4:1 on surface (light), 4.7:1 (dark).
light
dark
red-tintError pill and removed-line background; text red (13px medium+) or ink.

Charts

light · 4.2:1
dark · 6.5:1
series-1Chart series 1 — every chart's first series. Blue, in step with accent.
light · 8.2:1
dark · 2.7:1
series-2Chart series 2. The four series are a validated set: every pair clears CVD ΔE 9.
light · 2.5:1
dark · 4.8:1
series-3Chart series 3.
light · 3.3:1
dark · 5.0:1
series-4Chart series 4.

Tooltips

light
dark
tooltip-bgDark tooltip and chart-hover card fill in both themes.
light · 13.9:1
dark · 16.8:1
tooltip-fgText on tooltip-bg (14:1 / 17:1).
light · 6.2:1
dark · 7.8:1
tooltip-mutedSecondary text on tooltip-bg (6.3:1 / 7.9:1).
light
dark
tooltip-borderEdge of the tooltip card.

Controls

light
dark
control-onFill of a chosen selection control (switch track, checkbox, radio). ink — neutral, not blue.
light
dark
control-glyphThe check or dot drawn on control-on (16:1 light / 16:1 dark).
light
dark
control-trackOFF track of switches and sliders.
light
dark
control-thumbSwitch/slider thumb at rest: white in both themes so it reads on any track.
light
dark
control-thumb-onThumb on an ON track: white on the dark ink track in light; dark on the near-white ON track in dark.
light
dark
control-fillSlider range fill. Same as control-on, except dark where a mid grey keeps the thumb distinct.

Decoration

light
dark
stripeThe 1px diagonal stripe of the signature page background (.iris-stripes), drawn over stripe-bg.
light
dark
stripe-bgGround of the diagonal-stripe page background behind gallery and landing pages.
light
dark
gradient-fromLavender stop of the soft gradient wash used by the SiteHeader/SiteFooter blocks; text on it ink.
light
dark
gradient-toPeach stop of the same wash.
  • Ground: page behind everything; canvas for recessed working areas (chat canvas, card wells); surface for cards, inputs and menus; inset for wells inside a surface (code, table headers).
  • Text: ink for primary text and icons; ink-2 for secondary text (≥5.2:1 on every ground); ink-3 only for placeholders, disabled labels and decoration — it is 2.7:1 and never the only carrier of meaning.
  • Interaction: hover rows and quiet buttons with hover; pressed and ghost fills with hover-2; filled fields and pill buttons on field.
  • Accent: accent for focus outlines, selection, progress and the accent button; accent-ink for accent-coloured text and links; accent-tint for selected rows and chips. Text on an accent fill uses on-accent.
  • Status: green/green-tint success, orange/orange-tint warning, red/red-tint danger. On tints, set the label in ink or 13px-medium status colour.
  • Tooltips and chart overlays are dark in both themes: tooltip-bg with tooltip-fg and tooltip-muted.
  • Charts use series-1 (blue, in step with the accent) through series-4, in that order; never pick chart colours by hand.
  • Primary actions are ink filled with canvas text . Use the accent button for at most one call to action per screen.
  • Theme: set data-theme="dark" on <html>; every token has a tuned dark value — never invert or hard-code colours.

In code, every token is a Tailwind utility from src/styles/theme.css: bg-surface, text-ink-2, border-line, bg-accent-tint, fill-series-1, rounded-control, shadow-card, h-control-md, duration-fast, ease-standard. Tailwind's own colour palette is switched off, so only token colours exist.

Type

Display

display-hero72px / 1.1 · 500
Interfaces that think out loud

Landing hero heading at desktop (text-5xl). One per page.

display-152px / 1.1 · 500
Ship the agent, keep the craft

Hero heading on tablet; page heroes (text-4xl).

display-236px / 1.1 · 500
Everything your product needs

Landing section headings (text-3xl).

display-328px / 1.1 · 500
Plans that grow with you

Block titles, pricing headers (text-2xl).

display-422px / 1.1 · 500
Frequently asked

Small display titles inside blocks (text-xl).

Interface

title21px / 1.375 · 600
Interfaces that think out loud.

App page titles and the gallery rail headline.

heading17px / 1.4 · 600
Approve this change?

Card and dialog titles.

body-lg16px / 1.4 · 400
Every component installs as plain source.

Field text and long-form body (text-base).

body14px / 1.5 · 400
Streaming answers render word by word as tokens arrive.

Default interface text — what body is set in.

label13px / 1.4 · 500
Run tool

Buttons, tabs, menu items, segmented options: the most common interface size.

label-sm12.5px / 1.4 · 500
Synced 2 minutes ago

Compact labels, nav rows, chip text.

small12px / 1.5 · 400
Updated 2 minutes ago

Metadata, footers, table meta.

caption11.5px / 1.4 · 500
3 sources

Pills, counts, tool-chip captions.

micro10.5px / 1.3 · 500
BETA

Tiny status tags and chart ticks; uppercase only with ink-2 or stronger.

Code

code12.5px / 20px · 400
const reply = await agent.run(prompt);

Code blocks and diffs.

code-sm12px / 18px · 400
tool.call("search")

Inline code, tool-call lines, file paths.

  • Inter (--font-body) for all interface text, with font-feature-settings: "cv11", "ss01" and -0.01em tracking. Body copy is body (14px/1.5). Controls, tabs and menu items use label (13px/500); compact rows label-sm (12.5px); metadata small (12px); counts and pills caption (11.5px).
  • Geist (--font-display) for headings on marketing and landing surfaces only: display-hero (72px) once per page, display-1 (52px) page heroes, display-2 (36px) section headings, display-3/display-4 for blocks. Always weight 500, leading 1.1, tracking -0.03em.
  • App screens use Inter headings: title (21px/600, -0.02em) for page titles, heading (17px/600) for card and dialog titles. Don't put Geist inside dense product UI.
  • JetBrains Mono (--font-code) for code, file paths, tool calls and ID-like values: code (12.5px/20px), code-sm (12px/18px). Section numbers in galleries are 11px mono in ink-3.
  • Headings balance (text-wrap: balance), paragraphs pretty. Use tabular numerals for counters, prices and tables.

Spacing and layout

Spacing scale

space-1
4px
space-2
8px
space-3
12px
space-4
16px
space-5
20px
space-6
24px
space-8
32px
space-10
40px
space-12
48px
space-16
64px
space-20
80px
space-24
96px

Control heights

32pxcontrol-height-sm
36pxcontrol-height-md
40pxcontrol-height-lg
  • 4px base: space-1 4 · space-2 8 · space-3 12 · space-4 16 · space-6 24 · space-8 32 · space-16 64 · space-24 96.
  • Small primitive cards pad 12px (space-3); cards and dialogs 16px (space-4); gaps between cards 16–24px.
  • App shells: content max 960px for galleries and docs; marketing pages max 1120px with 32px gutters and 64–96px between sections.
  • Control heights: control-height-sm 32px, control-height-md 36px (default), control-height-lg 40px.

Shape and elevation

Radii

radius-chip · 6pxChips, tags, kbd, small icon tiles.
radius-control · 8pxInputs, selects, menu items, list rows.
radius-button · 9999pxEverything pressable: buttons, calls to action, tabs, segmented options, toggle chips, pagination and icon buttons. Pill-shaped; icon-only buttons become circles.
radius-card · 10pxCards and tables.
radius-window · 14pxWindows, dialogs, the prompt bar, landing-page frames.
radius-panel · 10pxMenus, popovers and panels — equal to radius-card.
radius-surface · 14pxLarge surfaces (sheets, hero frames) — equal to radius-window.
radius-pill · 9999pxPills: badges, switches, segmented controls, chips.

Pills in use — everything pressable

BoardTimeline Verified

Shadows

shadow-xs
shadow-sm
shadow-md
shadow-lg
shadow-hairline
shadow-btn
shadow-card
shadow-raised
shadow-overlay
shadow-inset-field
shadow-resting
shadow-floating
shadow-xs
shadow-sm
shadow-md
shadow-lg
shadow-hairline
shadow-btn
shadow-card
shadow-raised
shadow-overlay
shadow-inset-field
shadow-resting
shadow-floating
  • Radii: radius-chip 6px (chips, tags, kbd), radius-control 8px (inputs, selects, menu items, list rows), radius-card 10px (cards, tables, panels), radius-window 14px (dialogs, prompt bar, demo frames). Everything pressable is a pill — radius-button (rounded-button): buttons, calls to action, tabs, segmented controls, toggle and filter chips, pagination and icon buttons (icon-only buttons are circles). One exception: a button drawn as a keyboard key (an Esc cap beside a ⌘ K hint) keeps the key shape, radius-chip. Nest radii: an inner element is one step smaller than its container; pills inside pills stay pills.
  • Shadows are smooth multi-layer stacks: shadow-btn secondary buttons, shadow-card resting cards, shadow-raised hovered or raised panels, shadow-overlay menus, popovers and dialogs, shadow-hairline a flat 1px ring, shadow-inset-field pressed-in fields. Layers that draw their own border use the ring-free shadow-floating (menus, popovers, sheets) and shadow-resting (button hover).
  • In dark, elevation rings become faint white edges (already in the dark values); don't add borders to compensate.

Backgrounds

Page, canvas and the signature stripes

page
canvas
.iris-stripes
  • App and landing pages sit on page. The signature gallery background is .iris-stripes: a 1px diagonal stripe every 8px over stripe-bg, fixed to the viewport — use it only around a framed content sheet, never behind text.
  • No gradients in product UI. The soft lavender-to-peach wash (gradient-from → gradient-to) is reserved for the SiteHeader/SiteFooter blocks.
  • Imagery: real product screens or flat, neutral shapes in canvas/line-strong tones. No stock photos of people, no 3D blobs.

Motion and states

Easings — hover a card to run it

ease-enter
ease-standard
ease-exit
ease-in-out
ease-spring
ease-out-strong
ease-in-out-strong
ease-link

Durations

duration-instant
120 ms
duration-fast
160 ms
duration-standard
240 ms
duration-considered
480 ms
duration-spring
580 ms

Hover the panel to fill every bar at its own duration.

  • Easing: --ease-out-strong and --ease-standard for most transitions; --ease-enter for things arriving; --ease-spring for transforms with --duration-spring. Durations: --duration-instant 120ms, --duration-fast 160ms (hover, colour), --duration-standard 240ms (layout), --duration-considered 480ms (page-level reveals).
  • Press feedback: buttons press in with Motion's whileTap (0.96–0.985 by width); triggers of menus and popovers answer with colour only. Hover changes fill, never position.
  • Disabled: filled buttons drop to 52% opacity; outlined controls keep their shape and fade only the label to ink-3.
  • Loading states keep the control's width; text swaps morph rather than jump.
  • Theme switches freeze transitions for one frame (.theme-switching), so the flip is a single repaint.
  • Respect prefers-reduced-motion: transforms are skipped, fades shorten. Every component already does this — keep it in your own code.

Focus and accessibility

Keyboard focus — a 2px accent ring, offset 2px

Fields show focus with their borderReady status always comes with a word
  • Keyboard focus draws a 2px solid accent outline, offset 2px (≥3:1 on every surface in both themes). Pointer focus draws nothing. Text fields show focus with their border and caret; menu items and options with their highlight fill.
  • Status is never colour alone: pair green/orange/red with a word or icon.
  • Give every icon-only button an aria-label; charts take a label that describes the data.
  • Known source pairs below 4.5:1, kept exact: ink-3 on any ground; accent and on-accent (light) for small text; orange text on surface (light). Use them for marks, large or bold text only.

Iconography

lucide, stroke 1.75–2, sized to the text beside it

14px
16px
18px
  • lucide line icons (bundled), 14–18px, stroke 1.75–2, currentColor, round caps and joins. Size icons to the text they sit beside: 13–14px in 12–13px labels, 16px in controls, 18px in navigation.
  • Custom glyphs are inline SVG paths in the same stroke style.
  • No emoji in UI, no filled or duotone icon sets, no brand logos you don't have rights to. Sign-in blocks show Google, Apple and GitHub marks only as provider buttons.
  • Iris UI has no logo mark yet: set the name in plain type — Geist 600, -0.035em — as the header and footer blocks do.

Using the components

  • Load in order: tokens.css, the fonts, components/bundle.css, React 19 and ReactDOM (components/lib/), then components/bundle.js. Everything is on window.Iris.
  • Mount with ReactDOM.createRoot(el).render(React.createElement(Iris.Button, …)). Sub-parts are separate exports (Tabs, TabsList, TabsTrigger, TabsContent; Dialog, DialogTrigger, DialogContent…). Helper hooks are exported too (useToastStack, useDrawerItem).
  • The AI-interface components (ApprovalCard, ThinkingState, StreamingText, ToolChips, …) render a full demo with no props; always pass your own data and labels in product code.
  • Blocks are complete page sections; pass your own brand, copy and data — several have sample defaults. Start new landing pages from LandingProduct (marketing home) or LandingGallery (showcase).
  • Each component's card lists when to use it, its props and gotchas; read it before using the component.