---
version: alpha
name: nadicode Design System
description: >-
  nadicode's design system and its design authority for tokens, visual
  direction and the rules every component and page shares: every component,
  website section, and product screen in the website, the Portal, the Nadia
  desktop app, and the Alveare desktop app builds on it. A component's own
  contract is the usage page in its source in @nadicodeai/ui.

colors:
  action: "#1e3cff"
  action-foreground: "#ffffff"
  action-hover: "#1829f0"
  action-active: "#1425d3"
  ink: "#111111"
  body: "#666666"
  muted: "#6b6b6b"
  canvas: "#ffffff"
  canvas-soft: "#fafafa"
  canvas-soft-2: "#f5f5f5"
  material-workspace: "#fafafa"
  material-solid: "#ffffff"
  material-resting: "#ffffff"
  material-raised: "#ffffff"
  material-floating: "#ffffff"
  material-modal: "#ffffff"
  line: "#e5e5e5"
  input: "#757575"
  focus-ring: "#111111"
  scrim: "#f2f2f2"
  link: "#1e3cff"
  link-deep: "#1425d3"
  link-bg-soft: "#ebf5ff"
  info: "#1e3cff"
  info-soft: "#e8ecff"
  info-deep: "#0e1d8a"
  success: "#00a85a"
  success-soft: "#c6efd9"
  success-deep: "#00522c"
  error: "#d3302f"
  error-soft: "#fdeaea"
  error-deep: "#8f1a19"
  warning: "#ffc220"
  warning-soft: "#fff3d1"
  warning-deep: "#7a5800"
  navigation-selection-bg: "#ededed"
  navigation-selection-fg: "#111111"
  navigation-selection-hover: "#e5e5e5"
  state-ready: "#6b6b6b"
  state-running: "#1e3cff"
  state-review: "#ffc220"
  state-blocked: "#d3302f"
  state-complete: "#00a85a"
  identity-verde: "#00a85a"
  identity-rosso: "#d3302f"
  identity-arancio: "#ff5a1f"
  identity-giallo: "#ffc220"
  identity-cobalto: "#1e3cff"
  identity-azzurro: "#0f6ee8"
  identity-ink: "#000000"
  identity-white: "#ffffff"
  logo-dot: "#1e3cff"
  logo-guide: "#999999"
  chart-1: "#1e3cff"
  chart-2: "#e5468f"
  chart-3: "#6f42c1"
  chart-4: "#ff5a1f"
  chart-5: "#0096a7"
  chart-6: "#d3302f"
  chart-7: "#e2aa00"
  chart-8: "#00a85a"
  chart-9: "#7aa0ff"
  chart-10: "#ffabc9"

  cobalto-100: "#f3f7ff"
  cobalto-200: "#e9f0ff"
  cobalto-300: "#dde8ff"
  cobalto-400: "#cddcff"
  cobalto-500: "#aec7ff"
  cobalto-600: "#7aa0ff"
  cobalto-700: "#1e3cff"
  cobalto-800: "#1829f0"
  cobalto-900: "#1425d3"
  cobalto-1000: "#07117a"
  rosso-100: "#fff4f2"
  rosso-200: "#ffebe8"
  rosso-300: "#ffdfdb"
  rosso-400: "#ffcfc9"
  rosso-500: "#ffb0a7"
  rosso-600: "#ff7267"
  rosso-700: "#d3302f"
  rosso-800: "#c51e22"
  rosso-900: "#af1e20"
  rosso-1000: "#540004"
  arancio-100: "#fff4f1"
  arancio-200: "#ffebe5"
  arancio-300: "#ffe0d7"
  arancio-400: "#ffd0c2"
  arancio-500: "#ffb19a"
  arancio-600: "#ff835d"
  arancio-700: "#ff5a1f"
  arancio-800: "#f04c03"
  arancio-900: "#ac3300"
  arancio-1000: "#4c1200"
  giallo-100: "#fffaef"
  giallo-200: "#fef4e1"
  giallo-300: "#fdefd1"
  giallo-400: "#fde5b5"
  giallo-500: "#ffd989"
  giallo-600: "#ffcc5c"
  giallo-700: "#ffc220"
  giallo-800: "#e2aa00"
  giallo-900: "#8a6500"
  giallo-1000: "#422f00"
  verde-100: "#effaf2"
  verde-200: "#e4f6e8"
  verde-300: "#d4f0db"
  verde-400: "#bceac8"
  verde-500: "#78e29c"
  verde-600: "#37c171"
  verde-700: "#00a85a"
  verde-800: "#009a52"
  verde-900: "#00773e"
  verde-1000: "#003317"
  acqua-100: "#eff9fb"
  acqua-200: "#e4f4f6"
  acqua-300: "#d5edf1"
  acqua-400: "#bde5eb"
  acqua-500: "#7cd8e7"
  acqua-600: "#3fb7c8"
  acqua-700: "#0096a7"
  acqua-800: "#008998"
  acqua-900: "#00707d"
  acqua-1000: "#002f36"
  viola-100: "#f7f5ff"
  viola-200: "#f1edff"
  viola-300: "#e9e3ff"
  viola-400: "#ded6ff"
  viola-500: "#cbbbff"
  viola-600: "#ad8dff"
  viola-700: "#6f42c1"
  viola-800: "#6435b4"
  viola-900: "#582f9e"
  viola-1000: "#2f1659"
  rosa-100: "#fff3f7"
  rosa-200: "#ffeaf0"
  rosa-300: "#ffdee8"
  rosa-400: "#ffccdd"
  rosa-500: "#ffabc9"
  rosa-600: "#ff65a7"
  rosa-700: "#e5468f"
  rosa-800: "#d73783"
  rosa-900: "#ad1f66"
  rosa-1000: "#50002a"

  dark-canvas: "#000000"
  dark-canvas-soft: "#111111"
  dark-canvas-soft-2: "#1a1a1a"
  dark-material-workspace: "#000000"
  dark-material-solid: "#000000"
  dark-material-resting: "#0a0a0a"
  dark-material-raised: "#0f0f0f"
  dark-material-floating: "#0f0f0f"
  dark-material-modal: "#0f0f0f"
  card-graphite: "#151515"
  dark-ink: "#ededed"
  dark-body: "#a1a1a1"
  dark-muted: "#9e9e9e"
  dark-line: "#262626"
  dark-input: "#747474"
  dark-on-primary: "#0a0a0a"
  dark-action: "#1e3cff"
  dark-action-foreground: "#ffffff"
  dark-action-hover: "#1829f0"
  dark-action-active: "#1425d3"
  dark-link: "#a0bcff"
  dark-link-deep: "#cddcff"
  dark-link-bg-soft: "#0f223d"
  dark-focus-ring: "#ededed"
  dark-scrim: "#000000"
  dark-error: "#f5a3a1"
  dark-error-soft: "#380a09"
  dark-error-deep: "#fbcccb"
  dark-warning: "#ffd866"
  dark-warning-soft: "#402e00"
  dark-warning-deep: "#ffe7a3"
  dark-info: "#8098ff"
  dark-info-soft: "#070f47"
  dark-info-deep: "#b3c1ff"
  dark-success: "#17bd68"
  dark-success-soft: "#00351c"
  dark-success-deep: "#93e3b8"
  dark-state-ready: "#757575"
  dark-state-running: "#1e3cff"
  dark-state-review: "#ffc220"
  dark-state-blocked: "#d3302f"
  dark-state-complete: "#00a85a"
  dark-logo-dot: "#4d6bff"
  dark-navigation-selection-bg: "#1f1f1f"
  dark-navigation-selection-fg: "#ededed"
  dark-navigation-selection-hover: "#262626"
  dark-chart-1: "#4372ff"
  dark-chart-2: "#e95395"
  dark-chart-3: "#8a64de"
  dark-chart-4: "#f35822"
  dark-chart-5: "#2fa2b2"
  dark-chart-6: "#dd4640"
  dark-chart-7: "#ffc220"
  dark-chart-8: "#28ad62"
  dark-chart-9: "#a0bcff"
  dark-chart-10: "#ff9ac0"

  dark-cobalto-100: "#030b3f"
  dark-cobalto-200: "#040e50"
  dark-cobalto-300: "#061566"
  dark-cobalto-400: "#0a1b7d"
  dark-cobalto-500: "#1129a5"
  dark-cobalto-600: "#4372ff"
  dark-cobalto-700: "#1e3cff"
  dark-cobalto-800: "#1829f0"
  dark-cobalto-900: "#a0bcff"
  dark-cobalto-1000: "#ecf2ff"
  dark-rosso-100: "#2c0303"
  dark-rosso-200: "#390304"
  dark-rosso-300: "#490406"
  dark-rosso-400: "#5b0608"
  dark-rosso-500: "#790e11"
  dark-rosso-600: "#dd4640"
  dark-rosso-700: "#d3302f"
  dark-rosso-800: "#c51e22"
  dark-rosso-900: "#ffa096"
  dark-rosso-1000: "#ffedeb"
  dark-arancio-100: "#2a0600"
  dark-arancio-200: "#350900"
  dark-arancio-300: "#440f00"
  dark-arancio-400: "#541400"
  dark-arancio-500: "#721f00"
  dark-arancio-600: "#f35822"
  dark-arancio-700: "#ff5a1f"
  dark-arancio-800: "#f04c03"
  dark-arancio-900: "#ffa286"
  dark-arancio-1000: "#ffede8"
  dark-giallo-100: "#1c1200"
  dark-giallo-200: "#241800"
  dark-giallo-300: "#302100"
  dark-giallo-400: "#3c2b00"
  dark-giallo-500: "#523c00"
  dark-giallo-600: "#b88a00"
  dark-giallo-700: "#ffc220"
  dark-giallo-800: "#f1b500"
  dark-giallo-900: "#edb41a"
  dark-giallo-1000: "#faf1df"
  dark-verde-100: "#001a09"
  dark-verde-200: "#00220d"
  dark-verde-300: "#002d14"
  dark-verde-400: "#00381a"
  dark-verde-500: "#004d26"
  dark-verde-600: "#28ad62"
  dark-verde-700: "#00a85a"
  dark-verde-800: "#009a52"
  dark-verde-900: "#60da8d"
  dark-verde-1000: "#e6f7ea"
  dark-acqua-100: "#00181c"
  dark-acqua-200: "#001f24"
  dark-acqua-300: "#002a30"
  dark-acqua-400: "#00353c"
  dark-acqua-500: "#004952"
  dark-acqua-600: "#2fa2b2"
  dark-acqua-700: "#0096a7"
  dark-acqua-800: "#008998"
  dark-acqua-900: "#65d0e0"
  dark-acqua-1000: "#e6f5f7"
  dark-viola-100: "#170b2d"
  dark-viola-200: "#1e0f3b"
  dark-viola-300: "#29164c"
  dark-viola-400: "#331d5d"
  dark-viola-500: "#472b7c"
  dark-viola-600: "#8a64de"
  dark-viola-700: "#6f42c1"
  dark-viola-800: "#6435b4"
  dark-viola-900: "#c3afff"
  dark-viola-1000: "#f2efff"
  dark-rosa-100: "#2a0215"
  dark-rosa-200: "#36021b"
  dark-rosa-300: "#460225"
  dark-rosa-400: "#57032f"
  dark-rosa-500: "#740a41"
  dark-rosa-600: "#e95395"
  dark-rosa-700: "#e5468f"
  dark-rosa-800: "#d73783"
  dark-rosa-900: "#ff9ac0"
  dark-rosa-1000: "#ffecf2"

  category-teal-soft: "#e0f7f4"
  category-teal-deep: "#08665e"
  dark-category-teal-soft: "#062b28"
  dark-category-teal-deep: "#75e0d5"
  category-purple-soft: "#f3e8ff"
  category-purple-deep: "#7525a7"
  dark-category-purple-soft: "#2b143e"
  dark-category-purple-deep: "#d5a3f7"
  category-pink-soft: "#fce7f1"
  category-pink-deep: "#a32464"
  dark-category-pink-soft: "#381525"
  dark-category-pink-deep: "#f7a3cf"

typography:
  display-xl:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 72px
    fontWeight: 600
    lineHeight: 72px
    letterSpacing: -4.32px
  display-wide:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 64px
    fontWeight: 600
    lineHeight: 64px
    letterSpacing: -3.84px
  display-lg:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 56px
    fontWeight: 600
    lineHeight: 56px
    letterSpacing: -3.36px
  display-section:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 48px
    fontWeight: 600
    lineHeight: 56px
    letterSpacing: -2.88px
  display-md:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 40px
    fontWeight: 600
    lineHeight: 48px
    letterSpacing: -2.4px
  heading-lg:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 32px
    fontWeight: 600
    lineHeight: 40px
    letterSpacing: -1.28px
  heading-md:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 24px
    fontWeight: 600
    lineHeight: 32px
    letterSpacing: -0.96px
  heading-sm:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 20px
    fontWeight: 600
    lineHeight: 26px
    letterSpacing: -0.4px
  heading-xs:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 16px
    fontWeight: 600
    lineHeight: 24px
    letterSpacing: -0.32px
  heading-compact:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 14px
    fontWeight: 600
    lineHeight: 20px
    letterSpacing: -0.28px
  button-lg:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 16px
    fontWeight: 500
    lineHeight: 24px
    letterSpacing: 0px
  button:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 14px
    fontWeight: 500
    lineHeight: 21px
    letterSpacing: 0px
  button-xs:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 12px
    fontWeight: 500
    lineHeight: 16px
    letterSpacing: 0px
  label-display:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 20px
    fontWeight: 400
    lineHeight: 32px
    letterSpacing: 0px
  label-lg:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 18px
    fontWeight: 400
    lineHeight: 20px
    letterSpacing: 0px
  label-md:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 16px
    fontWeight: 400
    lineHeight: 20px
    letterSpacing: 0px
  label:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 14px
    fontWeight: 400
    lineHeight: 20px
    letterSpacing: 0px
  label-sm:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 13px
    fontWeight: 400
    lineHeight: 16px
    letterSpacing: 0px
  caption:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 12px
    fontWeight: 400
    lineHeight: 16px
    letterSpacing: 0px
  label-mono-lg:
    fontFamily: Geist Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, monospace
    fontSize: 14px
    fontWeight: 400
    lineHeight: 20px
    letterSpacing: 0px
  label-mono-md:
    fontFamily: Geist Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, monospace
    fontSize: 13px
    fontWeight: 400
    lineHeight: 20px
    letterSpacing: 0px
  label-mono:
    fontFamily: Geist Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, monospace
    fontSize: 12px
    fontWeight: 400
    lineHeight: 16px
    letterSpacing: 0px
  body-display:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 24px
    fontWeight: 400
    lineHeight: 36px
    letterSpacing: 0px
  body-lead:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 20px
    fontWeight: 400
    lineHeight: 36px
    letterSpacing: 0px
  body-lg:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 18px
    fontWeight: 400
    lineHeight: 28px
    letterSpacing: 0px
  body-md:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 16px
    fontWeight: 400
    lineHeight: 24px
    letterSpacing: 0px
  body-sm:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 14px
    fontWeight: 400
    lineHeight: 20px
    letterSpacing: 0px
  body-compact:
    fontFamily: Geist, Inter, system-ui, -apple-system, sans-serif
    fontSize: 13px
    fontWeight: 400
    lineHeight: 18px
    letterSpacing: 0px
  code:
    fontFamily: Geist Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, monospace
    fontSize: 13px
    fontWeight: 400
    lineHeight: 18px
    letterSpacing: 0px

rounded:
  none: 0px
  xs: 2px
  xs-plus: 4px
  sm: 6px
  md: 8px
  lg: 12px
  xl: 16px
  2xl: 20px
  3xl: 24px
  full: 9999px

spacing:
  xxs: 4px
  xs: 8px
  sm: 12px
  md: 16px
  lg: 24px
  xl: 32px
  2xl: 40px
  3xl: 48px
  4xl: 64px
  5xl: 96px
  6xl: 128px
  guide: 1px
  icon-stroke: 1.5px
  icon-stroke-sm: 1.25px
  focus-outline-width: 2px
  focus-outline-offset: 2px
  control-height: 36px
  control-height-sm: 32px
  control-height-lg: 40px
  control-height-xs: 24px
  control-padding-inline: 12px
  button-padding-inline-sm: 6px
  button-padding-inline: 10px
  button-padding-inline-lg: 14px
  card-padding: 20px
  touch-target: 44px

---

## Overview

This file is nadicode's design system and its design authority for tokens, visual direction and the rules every component and page shares; a component's own contract is the usage page in its source in `@nadicodeai/ui`. The tokens above are the values; the sections below are the rules for applying them. Every component in `@nadicodeai/ui`, every website section, and every screen in the Portal, Nadia and Alveare builds on it. Another document may say how one product applies a rule, and links here for the rule; it never restates one. A shared rule that is missing here is added here first; a rule of one component is added to its usage page.

nadicode puts AI Agents to work inside small and mid-sized companies. Its sites and products read as a technology company with a real product: the interface is neutral and exact, the product is shown working, and colour and depth give the page its energy. Three ideas carry every screen:

- **Neutral structure.** Chrome, controls and text sit on an achromatic spine, so a desaturated screen loses nothing structural. The strongest contrast on a screen belongs to the action.
- **Colour as stage.** The five identity hues appear at full strength only in the places "Colour as stage" lists, the colour of Agents among them, and never as a section's ground. They carry the brand; they never decorate a control. Semantic colour identifies statuses, categories and named actions through the component palette below.
- **Real depth.** Surfaces sit at four heights. Content lies on the page with a 1 px edge and no grey fill; what floats takes its depth from light: deeper shadows in light, and in dark a line of light along its top, a brighter edge and a deep black shadow. Glass goes only where something moves under it.

The products are still: only Agents move on their own. The website moves with purpose: the product at work, Agents and its few shader objects ("Motion").

The products are assembled in three layers, each with one owner and one check, so the same job looks the same on every page:

- **Components.** Every component in `@nadicodeai/ui` has a usage page: when to use it, what to use instead, how it behaves, its words and its accessibility, written in the component's own source and rendered by the package's showcase ("Component selection and usage").
- **Compositions.** A page is built from one of nine compositions, each a component in `@nadicodeai/ui` with fixed slots, so the page supplies only content and the order of its actions and the place of its three dots cannot drift: the five page compositions, the record row, the empty state, the overlays and the identity marks ("Page compositions").
- **Checks.** Four lint rules fail the build in the Portal, Nadia's window and Alveare Server: a page renders a composition (`page-composition`), an absence is only `EmptyState` (`empty-state-owner`), commands go only through `PageActions`, `ActionMenu` and `AccountMenu` (`menu-owner`, with the menu modules held by `no-restricted-imports`), and a title or a label carries no icon (`no-decorative-icon`). A page that broke a rule when the checks arrived sits on its product's recorded baseline, whose count may only fall.

One foundation serves four registers. The foundation is every value in this file. A register is one product's way of applying it: the website, the Portal, the Nadia desktop app and the Alveare desktop app ("Registers"). A register adds rules and chooses which values it uses; it never redefines a value or mints one. A value one register needs is authored here under `register.<name>`, and a value two registers share moves to `core`.

## Colors

### Neutral structure

The neutral spine carries grounds, chrome, text and every control: `{colors.ink}`, `{colors.body}`, `{colors.muted}`, `{colors.canvas}`, `{colors.canvas-soft}`, `{colors.canvas-soft-2}`, `{colors.line}`, the material tones, and their dark pairs. Every neutral is an equal-channel grey (OKLCH chroma 0) in both themes, so no tint paints a surface. Colours are authored here as sRGB hex and emitted as `oklch()`.

- **Action** (`{colors.action}`, `{colors.action-foreground}`, `{colors.action-hover}`, `{colors.action-active}`): the primary control is cobalto with a white label in both themes, its hover and press the next two cobalto steps; the shadcn `primary` role maps here, and a page holds one, so the brand's colour marks the one thing to press.
- **Text**: `{colors.ink}` for headings and primary text, `{colors.body}` for supporting text, which clears AA on every canvas. `{colors.muted}` is for placeholders and disabled or inactive text only, never for descriptions, labels or help. These roles distinguish purpose, not a guaranteed contrast step: their similar neutrals keep supporting text and placeholders near the Geist reference while both meet our AA text floor.
- **Lines**: `{colors.line}` is the one decorative 1 px divider and is deliberately quiet. `{colors.input}` is the boundary of a field or any control whose edge identifies it (an input, a select, a checkbox, a radio), and clears the 3:1 non-text floor. A button is identified by its label, so an outline or secondary button's edge is the `{colors.line}` hairline, never `{colors.input}`. Never darken a decorative line locally to make it act as a control edge. Each border is drawn once: a divider sits inside the border of the surface that holds it and never doubles an edge where the two meet.
- **Focus** (`{colors.focus-ring}`): a full-opacity outline of `{spacing.focus-outline-width}` at `{spacing.focus-outline-offset}`, the maximum-contrast neutral on each ground, never a box-shadow ring and never a hue.
- **Blue on a choice** is info and appears only where Geist puts it: the on switch, the filled part of a slider, and a selected choice tile (border, tint, words, corner mark). Checkbox, radio, segmented control and toggle group stay neutral, and a disabled switch or slider fill turns grey, never a faded blue.
- **Interaction states** are contrast steps on the spine. Hover takes the `accent` role (`{colors.canvas-soft-2}`) and a selected row the `muted` role (`{colors.canvas-soft}`); a control's own hover is a step inside its family, as `{colors.action-hover}` is. Persistent navigation marks the current destination with `{colors.navigation-selection-bg}` and `{colors.navigation-selection-fg}`, and local tabs mark the current view with the same pair through the `selection` role. A segmented choice, `SegmentedControl` and the theme switcher's tiles, marks its chosen option with that pair too (with the resting elevation), and the fill never comes without its foreground: on the `selection` ground, a radio's on-primary label reads white on near-white in light and black on black in dark.
- **Scrim** (`{colors.scrim}`): the overlay behind dialogs, sheets and drawers, never blurred. In light it is a near-white wash at the `scrim-opacity` material value, as Vercel's is; in dark it is black at `dark-scrim-opacity`, never grey. The page behind steps back and the panel in front keeps its full contrast.

### Colour as stage

The identity palette is the brand's colour: cobalto `{colors.identity-cobalto}`, verde `{colors.identity-verde}`, arancio `{colors.identity-arancio}`, giallo `{colors.identity-giallo}` and rosso `{colors.identity-rosso}`, with `{colors.identity-ink}` and `{colors.identity-white}` as the poles type sits on. Azzurro `{colors.identity-azzurro}` has two jobs: the first stripe of Nadia's ribbon, and a band of a company's card foil ("Company card").

The hues appear at full strength and flat, with no tints, steps, gradients or hover variants, in these places. The one exception is the website's shader objects, each the material of one object and never a section's ground: the threads she holds, a fan of lines in her stripes from her orb to her colleagues, on black; Alveare's hive, cells that light in its hues as the work passes; and the company card's holographic foil ("Company card"). Stripes and cells stay pure bands of their hues, never a colour between two of them. Ruled by the founder: "shaders are not backgrounds all over the place".

- **Section grounds on the website.** A section's ground is white, soft or black, and two neighbouring sections never share one; colour lives in the pictures, the objects, the Agents and the shader objects, never as a section's ground.
- **Glass.** The colour a glass surface shows through ("Elevation & Depth").
- **Accents on the website.** A 2 px rule that marks one item of a set, one hue per item.
- **Agents.** The orbs ("Agent orbs").
- **Objects.** The saturated pieces of the art direction's objects, one per benefit ("Iconography").
- **The logo.** The dot of the wordmark and the icon tile ("Brand media authority"), and the compact company identity marks ("Identity marks").
- **Imagery.** Art direction reads these tokens and keeps no private palette.

Text on a colour ground is the pole that clears AA on that hue: `{colors.identity-white}` on cobalto and rosso; `{colors.identity-ink}` on verde, arancio and giallo. A control on a colour ground uses the button's on-colour variants ("Components"). Product chrome in the Portal and Nadia stays neutral: grounds, text, lines and navigation, a selected row included, whatever it opens. Colour inside that structure is the identity palette at full strength in both themes: the primary control and the on switch in cobalto, feedback and state in cobalto, verde, giallo and rosso with their pole for text, and links in cobalto, the dark theme's links and coloured text one light step of the same hue. It also identifies Agents, providers and categories; an Agent's own colour is her orb, so her row in a list never takes a hue. Choose a typed component tone from the semantic palette; never paint a control with an arbitrary identity hex.

### Meaning

- **Feedback** pairs a hue with a soft ground and a deep text step: success (verde), warning (giallo `{colors.warning}`), error (rosso `{colors.error}`), info (cobalto `{colors.info}`). The `-soft` value is the tinted ground of a tag or banner and the `-deep` value is the AA text on it; a component reaches the pair through the `destructive-soft`, `warning-soft`, `success-soft` and `info-soft` roles and their foregrounds. On a solid fill, error takes white text and warning and success take ink in light; in dark all three take `{colors.dark-on-primary}`.
- **Link** (`{colors.link}`, `{colors.link-deep}`, `{colors.link-bg-soft}`): blue inline links. Success uses its own green roles; link colour never doubles as a health signal.
- **Workflow states** (`{colors.state-ready}`, `{colors.state-running}`, `{colors.state-review}`, `{colors.state-blocked}`, `{colors.state-complete}`) colour non-text marks only: meter fills and trend icons. Cobalto means in motion and verde means done. A state's word is `StatusBadge`'s, in the foreground colour; never put a raw meter-fill token on text.
- **Status** is always a `StatusBadge` tone with its shape and a word ("Components").

### Category colour

Labels and their icons may carry blue, teal, purple or pink to distinguish named categories. `Tag` owns these categorical tones; `Badge` also offers the teal, purple and pink soft fills with their contrasting foregrounds, and no status fill. The same category keeps the same tone across a surface and after sorting. A word remains visible, and a colour never invents status. Green, amber and red remain feedback meanings; the five-tone `StatusBadge` owns those. Icons inherit the tone of their label rather than adding a second colour. Brand marks keep their genuine colours. This uses Geist’s light fill / strong text technique with nadicode’s semantic roles; agents never choose raw palette values in pages.

### Scales

Eight hue scales give product colour its steps, on the structure of [Geist's colours](https://vercel.com/geist/colors): cobalto, rosso, arancio, giallo, verde, acqua, viola and rosa, each from `{colors.cobalto-100}` to `{colors.cobalto-1000}` with a `dark-` value for every step. Step 700 is the hue itself: the identity hex for cobalto (`{colors.cobalto-700}`), rosso, arancio, giallo and verde, the categorical teal and violet for acqua and viola, and a pink beside the category pink for rosa. Neutrals stay on the spine above; there is no grey scale.

| Steps | Job |
| --- | --- |
| 100, 200, 300 | A tinted ground: default, hover, active |
| 400, 500, 600 | A border or a soft mark: default, hover, active |
| 700, 800 | A solid fill and its hover |
| 900, 1000 | Text and icons: secondary, primary |

Step 900 clears AA text on its own 100, 200 and 300 and on the canvas in both themes, and the build refuses a scale that loses it. Lightness climbs with the step in light. In dark, as in Geist's dark scales, 600 is the brightened mark a chart draws on black and sits above 700 and 800, and giallo's 900 sits below its 700, so a dark ramp is chosen by step, not by assuming lightness order. A step reaches a page through a role or a component tone, never as a raw hex. The scales are product colour: a place in "Colour as stage" keeps its identity hue at full strength, and no scale step tints it.

### Dark mode

Every token `X` with a `dark-X` pair takes the dark value under a `.dark` ancestor; a component uses the same role in both modes and never ships a `dark:` utility or a mode branch of its own. The dark page is true black, `{colors.dark-canvas}`. Elevated surfaces lift from it by tone ("Elevation & Depth"); the soft steps `{colors.dark-canvas-soft}` and `{colors.dark-canvas-soft-2}` are interaction fills and never become a page, section or panel ground. On a lifted surface the hover and muted fills step up from that surface's own tone. Dark feedback and chart values are brightened so they read on black; `{colors.dark-input}` stays brighter than `{colors.dark-line}` so a field's edge clears 3:1. A surface that is dark by nature, such as Nadia's entry screen, a terminal console, a black website section or a black brand plane inside a product (`BrandField`), stays dark in every theme as one fixed treatment: it scopes `.dark`, so every role and control inside it takes its dark value.

### Roles

The shared colour roles are authored once, in the token extension's `semantic` group: each role (`background`, `card`, `popover`, `primary`, `muted`, `accent`, `border`, `input`, `ring`, `sidebar`, the feedback roles `destructive`, `warning`, `success` and `info` with their `-soft` pairs, the chart roles, and the rest) names one core token per mode. The Portal, the website and `@nadicodeai/ui` paint with these roles; Nadia's theme translates Hermes's role names onto the same ones and adds the shared roles Hermes has no name for. A product needing a role that does not exist adds it here; it never picks a colour locally.

## Data Visualization

### Chart source

Every chart and KPI card is a stock shadcn registry component owned by `@nadicodeai/ui`; a page composes them and never assembles a chart card or reaches into a chart's internals. The package keeps a short, tested list of deliberate deviations from the registry. The visual ones are rules:

- The tooltip prints the exact figure in the surface's units; the axis prints a scale (`1.6B`, `800M`).
- The value axis is always drawn, with five ticks on a 1, 2, 2.5, 5 or 10 step.
- Bars are capped in width, the grid is dashed in both directions, and plot heights are fixed.
- A multi-series chart has a legend below the plot, left-aligned and wrapping: a round dot and the entity name, in series order. A usage summary instead identifies its series through the adjacent ranking's identity labels and coloured share bars, without dots.
- A signed comparison keeps a visible zero line so losses and gains share one baseline, and colours each bar by its consequence: `success` where the value is favourable, `destructive` where it is not.
- A chart is a named section: its card title is the heading that names the plot.
- Every chart card has one header and footer: `ChartCard`, whose usage page holds them. A usage summary keeps its period and metric selectors beside its title; other period choices sit on the page's title row where the page has one. A daily axis labels every second day from the first, fewer in a half-width card. `ChartRanking` owns a usage summary's ranking and complete-list disclosure; other compact rankings remain one line per entity. A category axis is as wide as its longest name up to 128 px, a longer name wrapping to a second line where its row holds two lines and cut with an ellipsis where it does not; a short window's activity calendar draws larger cells and shares its row with a sibling card, never a lone narrow card.

No pie, donut, radar or dual-axis chart, and no mark or logo inside a legend. The one ring is `Gauge`, a control for a single ratio out of 100 against a fixed maximum, not a chart.

### Palette and assignment

Ten categorical series colour data and nothing else, in this order: cobalto, rosa, viola, arancio, acqua, rosso, giallo, verde, then a light cobalto and a light rosa, `{colors.chart-1}` to `{colors.chart-10}`. Each slot is a step of a scale ("Scales"). In light it is the 700 step, with giallo at 800 so a yellow bar keeps an edge on white, and the tints at cobalto-600 and rosa-500. In dark it is the 600 step, with giallo at 700 and the tints at cobalto-900 and rosa-900, and every slot clears 3:1 on black. Cobalto leads, as in the logo.

The order is the colour-blind safety for marks drawn in slot order, as a stack, a group and the legend draw them: the build holds every neighbouring pair apart in normal and deuteranopia-simulated vision in both themes (CIEDE2000 20 and 15) and refuses a slot that is not a scale step. Slots further apart carry no such floor: cobalto and viola (slots 1 and 3) stay close for a deuteranope, and a chart that draws a later slot beside an earlier one, such as a second ranking on a surface whose map follows the first, can put close slots side by side. Giallo and the two tints sit below 3:1 on white. A chart therefore relies on its always-on legend and its tooltip, never on hue alone.

Rosso enters at slot 6 and verde at slot 8, so a surface whose map holds five entities or fewer never shows a red or a green that reads as error or success; past five, the legend's words carry the meaning and the hue only tells series apart.

- **One series**: `{colors.chart-1}`, titled, with no legend.
- **Two to ten**: the slots in order, identified by the legend or the usage summary's ranking.
- **More than ten**: the eleventh entity and every later one fold into one neutral "Everything else" in `{colors.muted}`; the page lists its entities largest first, so the tail is the smallest.

A hue follows its entity, never its rank or position: one assignment per surface, the same in a chart and its table, stable under filtering and sorting.

### Meters, deltas and labels

- **Meter** and **Gauge**: `Progress` for consumption against a cap and `Gauge` for one ratio out of 100 where the comparison is the point; their usage pages hold their tones, stops, overage and figure. Neither has an indeterminate form: an unknown wait is the waiting line.
- **Delta**: an outline Badge with a trend icon (`TrendingUp`, `TrendingDown`, `Minus`) and the signed value, followed by a muted "vs {period}". Direction crossed with whether up is good sets the tone; a neutral direction stays neutral. Never bare signed text, colour alone, or a ▲▼ glyph. A figure with no comparison shows no badge.
- **Labels**: legend, axis and label text are ink roles and never wear the series colour. A single series is titled, not legended. Tooltips enhance a chart and never hold the only copy of a value; where a page needs the numbers as text, it shows a real table built from the same data.

## Typography

Geist and Geist Mono are the two families. The role scale follows the measured [Geist typography](https://vercel.com/geist/typography): headings, buttons, single-line labels and multiline copy each have their own line height and weight. The `label-mono` family and `code` use Geist Mono. Fonts ship with the package and their SIL Open Font License; no network font fetch is required. Text is antialiased in every product and on the website, as Vercel's is: grayscale smoothing (`-webkit-font-smoothing: antialiased`, `-moz-osx-font-smoothing: grayscale`), set once by the package's stylesheet.

| Token | Size / line height | Use |
| --- | --- | --- |
| `{typography.display-xl}` | 72 / 72 px | Largest display heading. |
| `{typography.display-wide}` | 64 / 64 px | Wide display heading. |
| `{typography.display-lg}` | 56 / 56 px | Hero headline. |
| `{typography.display-section}` | 48 / 56 px | Large section heading. |
| `{typography.display-md}` | 40 / 48 px | Section headline. |
| `{typography.heading-lg}` | 32 / 40 px | Large product section. |
| `{typography.heading-md}` | 24 / 32 px | Page or object title. |
| `{typography.heading-sm}` | 20 / 26 px | Card, settings section or dialog title. |
| `{typography.heading-xs}` | 16 / 24 px | Compact card or group title. |
| `{typography.heading-compact}` | 14 / 20 px | Dense subsection title. |
| `{typography.button-lg}` | 16 / 24 px | Large button label. |
| `{typography.button}` | 14 / 21 px | Default and small button label, only inside a component that renders a button. |
| `{typography.button-xs}` | 12 / 16 px | Label of a tiny button inside a field, nowhere else. |
| `{typography.label-display}` | 20 / 32 px | Large single-line label beside an icon. |
| `{typography.label-lg}` | 18 / 20 px | Prominent single-line label beside an icon. |
| `{typography.label-md}` | 16 / 20 px | Object or item label on one line. |
| `{typography.label}` | 14 / 20 px | Menu, navigation and field label on one line beside an icon. |
| `{typography.label-sm}` | 13 / 16 px | The secondary line under a label, with tabular digits. |
| `{typography.caption}` | 12 / 16 px | Tertiary label or badge. |
| `{typography.label-mono-lg}` | 14 / 20 px | Mono beside text larger than 14 px; a default `Kbd`. |
| `{typography.label-mono-md}` | 13 / 20 px | Mono beside 14 px text. |
| `{typography.label-mono}` | 12 / 16 px | Compact identifier; a `sm` `Kbd`. |
| `{typography.body-display}` | 24 / 36 px | Large marketing lead. |
| `{typography.body-lead}` | 20 / 36 px | Marketing lead. |
| `{typography.body-lg}` | 18 / 28 px | Long lead or quotation. |
| `{typography.body-md}` | 16 / 24 px | Copy for roomy views: dialogs and entry instructions. |
| `{typography.body-sm}` | 14 / 20 px | The default for multi-line copy and product descriptions. |
| `{typography.body-compact}` | 13 / 18 px | Tight secondary copy. |
| `{typography.code}` | 13 / 18 px | Inline code or code block. |

A token has one size at every width; nothing is sized in `vw` or `vh`. A role includes family, size, line height, weight and tracking together. Use the shared `nc-type-*` classes or the generated `text-<token>` utilities, such as `text-display-md`, with `font-label-mono` for the `label-mono` family and `font-code` for `code`, rather than assembling role metrics at a call site. Existing `text-xs`, `text-sm` and `text-base` body sizes match `caption`, `body-sm` and `body-md`. Card, panel and section titles are sentence-case Geist. Headings use weight 600 and their optical negative tracking. A softer run inside a heading uses 500; a strong run inside a label uses 500; strong paragraph copy uses 550. A softer or stronger run is made by nesting a `<strong>` under the type role, whether the role is written as `nc-type-<role>` or as the `text-<role>` utility; a second weight class on the `<strong>` is never added. Labels align single lines and icons; paragraphs use the copy roles and can wrap. Headings and the heading and display roles wrap balanced, so no line of a title is left short; copy wraps pretty, so a paragraph never ends on a lone word; centred copy wraps balanced, whatever its role. The type layer applies this, Nadia's skin takes it with the roles and Alveare's type adapter with its type steps, so a screen sets text wrapping only to depart from it. Numeric comparisons use tabular digits, and identifiers use mono. Semantic heading levels express document structure independently of visual size. Never shrink type to fit: wrap, reflow or move supporting detail.

### The website's text

The website keeps one token per role on every page: a hero's title `{typography.display-lg}` (`{typography.display-md}` under 640 px), a section's title `{typography.display-md}` (`{typography.heading-lg}` under 768 px), a lead `{typography.body-lg}` at a measure of 60 characters under a section's title, 52 in the closing band, whose row it shares with the band's object, and `40rem` in a hero, a card's or a tour stop's title `{typography.heading-md}`, an item's title `{typography.heading-sm}` and its words `{typography.body-md}`, and a product view's caption `{typography.label-mono}`. A section's words start at the column's edge; only a page's hero and its showcase centre them. A page's calls to action are the large button, in its hero and its closing band; every other way onward is a link with an arrow.

### Entry screens

Installation, startup, sign-in and recovery use `{typography.body-md}` for their
instructions, greetings and status messages, at its regular weight. A genuine
screen title may use `{typography.heading-sm}`; a changing status or an
instruction never becomes a heading merely because the screen has little copy.
Actions and stage lists use `{typography.body-sm}`, and progress counts use
`{typography.label-mono}`. These roles retain their token sizes at every width.

An entry page is a whole page with one job and no product chrome: no sidebar,
no breadcrumb, no footer. Each product has one entry frame, and every entry
page renders it: the Portal's `PortalFunnelFrame` with `AccountEntryHeader`
(sign-in, sign-up, an invitation, and the pages that hand a person to Nadia,
which stand on the dark plane with her ambient orb at 96 px over the card),
Nadia's `EntryScene` (her entry, update and installer screens, her orb at
160 px, 96 px under 640 px wide or 600 px tall) and Alveare Server's
`HiveScene`.

Every pairing in Nadia, a phone linking to her or to one of her Agents
(WhatsApp, Telegram, WhatsApp calls), is one step: a form drawer whose body
opens with her `EntryScene` contained at 16:9, saying only its short status
("Connecting Telegram", "Linked", "Code expired"), then the code or QR the phone
takes with its expiry, then the steps on the phone. A longer sentence stands
under the scene, never in it. The full-window scene stays for sign-in, start and
update.

A single-task entry screen centres the visible identity, message and actions as
one group in the usable window, horizontally and vertically. Hidden content and
reserved empty rows do not contribute to that group's height. A progress screen
may reserve the lines its status needs to prevent movement between stages, and
its measured progress is a written count in `{typography.label-mono}`, "Step 2
of 3", never a percentage. A picture stands clear of the task and whole, its
feet on the window's floor, inert and hidden from assistive technology, and
only from `lg`: below `lg` the picture leaves and the task keeps the screen. An
image edge must not masquerade as a finished object edge. Compact layouts
preserve readable controls and scroll when content cannot fit.

Where a picture may stand:

| Place | Picture |
| --- | --- |
| Entry: sign-in, sign-up and welcome, an invitation, handing a person to Nadia, Nadia's entry and update screens, the installer | Yes |
| A moment: a failure page, each app's card on Downloads, a product's first run, a collection's first use, Nadia's pairing of a phone | Yes |
| A funnel task: two-factor, an interrupted setup, a legal page, impersonation, a connection's done page | No: the task is enough |
| Any other page inside a product's chrome | No, except an address that is not there, whose picture stands centred in the content ("Failure surfaces") |

## Layout

Every gap, pad and stack step a page, section or composition sets is a rung of the spacing ladder, `{spacing.xxs}` 4 px through `{spacing.6xl}` 128 px; a layout picks the rungs it needs and never interpolates between them. Inside a component, the stock shadcn registry's own spacing stands. Every visible line has one owner: a seam is `{spacing.guide}` in `{colors.line}`, a control edge is `{colors.line}`, a field's and a button's alike, and focus is the focus outline.

Control geometry is the same in every product:

| Token | Owns |
| --- | --- |
| `{spacing.control-height}` | The default control, and every control in product chrome but a tab |
| `{spacing.control-height-xs}`, `{spacing.control-height-sm}`, `{spacing.control-height-lg}` | The 24, 32 and 40 px sizes a control and a field share |
| `{spacing.control-padding-inline}` | A field's inline padding (input, select, textarea, combobox, hover card) |
| `{spacing.button-padding-inline-sm}`, `{spacing.button-padding-inline}`, `{spacing.button-padding-inline-lg}` | A button's inline padding at its small, default and large size |
| `{spacing.touch-target}` | The minimum target on coarse or non-hover input, whatever the visual size |
| `{spacing.icon-stroke}`, `{spacing.icon-stroke-sm}` | The icon line ("Iconography") |
| `{spacing.focus-outline-width}`, `{spacing.focus-outline-offset}` | The focus outline |

Product chrome uses the 36 px control: a page header, a toolbar, a card's header and footer, a filter row, a collection row's actions and the three-dot trigger take `{spacing.control-height}` with a `{typography.button}` label. Compact controls use 32 px and prominent controls 40 px; a button's `sm` and `xs` sizes are for a control nested inside dense content, such as a notice's or a banner's one action, a descriptive item's trailing controls, a composer or an input group, and never for chrome. A control nested in a field, and `SegmentedControl`'s options, keep the same outer heights; their usage pages hold how.

### The website column

A website page is a sequence of full-bleed sections, each centring its content in one column, `.nc-page`: `content-max` 1200 px wide with a side gutter of 20 px below 640 px, 40 px from 640 px and 64 px from 1024 px. From 1024 px the column's two edges are drawn as 1 px rails in `line` through every section, and each section closes with a 1 px line across the full width, so the page reads on one visible grid. Sections sit one step apart, 64 px below 1024 px and 96 px from it (`.nc-section-step`); a full-bleed picture that closes a section takes the step on its leading edge only (`.nc-section-step-lead`). Inside the column a section composes a heading, a split, a bento, a row of cards, a step rail or a logo row, and a composition may change its column count at 768 px. Nothing else sets a page width, and the page never scrolls sideways: horizontal overflow is clipped at the root, never on an inner surface.

These values are the `register.website.layout` tokens; the breakpoints live in `foundation.css`, which collapses them into `--nc-gutter` and `--nc-section-step`.

### Product shells

The Portal, Nadia and Alveare are product shells: persistent navigation on the left, the selected destination on the right, on the workspace ground ("Elevation & Depth"). They use the spacing ladder and the control geometry, not the website's column. Every product answers "where am I?" the same way, with one place for each kind of navigation, so a person who moves between the Portal and Nadia in one afternoon finds every control where the other product put it:

| Kind | Its one place | Portal | Nadia | Alveare |
| --- | --- | --- | --- | --- |
| Where I can go | The sidebar | Groups by viewer, one selected row | Find, Assignments, Scheduled tasks, the company's Wiki and Skills, then the Agents | Inbox, Channels, Direct messages |
| An object's sections | The product’s documented section navigation | Customer, Partner and Agent tabs below the top bar; Installation sections in the sidebar | Two swaps only: ⋯ → Edit and Settings | The channel's own panels |
| The path | The top bar | Customer or Partner switcher on the left and the current section centred; other paths centred | The window header: the Agent's orb and name, then the chat switcher; on a nested page, a back arrow to the place above, then the path | The channel header names the channel |
| Find, ⌘K | The top of the sidebar | An outlined field | At the top of the list | Buzz's search, in its sidebar |
| The account | The sidebar's foot | With the bell | Under the update card | The package's `AccountMenu` |
| Getting the app | Above the account | Downloads, an outlined row | The update card, only when there is one | The update card |
| The page's actions | The page's title row | Secondary, primary, the three dots | The window header's trailing edge: the Agent's three dots, after the chat switcher | The channel header's trailing edge |

- **The workspace stays in view while working on a company.** In the Portal, Customers and Partners keep the workspace sidebar and use the `TabsNav` band below the top bar. A directory or search result opens the first tab, a Customer's Company or a Partner's Overview; switching companies keeps the current section when available and otherwise opens that first tab. Customer tabs are Company, Agents, Subscriptions, Members and Activity, reduced to the viewer’s authority; a Customer's own settings are `ObjectPage` entries of its Company tab that open in place, as Vercel's deployment page opens its steps, never a Settings tab or tabs inside a tab. Partner tabs are Overview, Customers, Finances, Members, Activity and Settings. Related views, such as Finances’ Commissions and Bulk pool, use line tabs inside their section. Agents also keep the workspace sidebar with their six local tabs. Installations retain `SidebarSections`; no object adds a second sidebar. A company's first tab starts with one `ObjectPage` identity card, its subscription state and Agent counts, then, for a Customer, its parts as entries that open in place, then its scoped charts and actionable issues. Subscription status summarizes member-account subscriptions; Partner company figures, assigned Customer figures, commissions and bulk-pool credit stay distinct.
- **An area whose pages are siblings opens as a sub-menu, as Vercel's sidebar does.** Vercel marks such a row with a › at its end ("Logs", "Observability", "Firewall", "Settings"), and its press replaces the places with one row, a ‹ at its start and the area's name centred, above the area's pages as words alone, the open one filled like a selected row; a row that only opens a page has no ›. In the Portal these areas are Settings (nadicode), Company (whoever manages a company) and Library (everyone who sees it: Agent templates, Skills, Plugins), each with its › row, and Account, opened from the account menu; their pages are never tabs (`SidebarSections` without an identity, its row ending in `SidebarMenuChevron`). “Tabs imply the views share scope, URL parent, and data model” (https://vercel.com/geist/tabs): Library's pages hold different objects with different lists and different ways to add, so they are a sub-menu, as Vercel's AI Gateway is. A destination whose pages are views of one reading, Analytics, keeps its tabs, as Vercel's Analytics and OpenRouter's Activity do. The ‹ returns to where the person opened the area from.
- **The top bar holds navigation, never the page's actions.** Customer and Partner collections and their object sections use `ScopeSwitcher`'s navigation variant on the left: the company's mark and name, a searchable list with the current company checked, and a close link back to the originating collection. The current section is centred, below the switcher on phones so the company name and status remain visible. Other pages keep the centred path, ending at the place or the object. An object's own status sits beside its name in the path and nowhere else on the page.
- **Find goes to places.** It opens from the outlined field at the top of the sidebar and on ⌘K, and names every sidebar place, chat and settings page; it never runs a command. Its field names what it searches, never a bare "Search"; ⌘K or Ctrl+K opens it and no other field takes that binding; a shortcut on a row is a `CommandShortcut`; and a polite live region reads how many results remain as the person types.
- **The account lives at the sidebar's foot.** `AccountMenu` names who is signed in and for whom, opens the account's settings from its gear, and holds Theme, Language and Sign out; the offer to get or update the app stands just above it.
- **Navigation names stable destinations.** An object opened from search or a link still shows its parent in the path; a return link remembers a visit and never replaces the parent. A scope selector keeps the current section when the next object has it, and otherwise opens its overview.
- **Compact screens keep the same relationships.** Tabs scroll horizontally, primary content stacks before supporting facts, and no second sidebar appears.
Nadia's window, its list, its Settings and its side panel are Nadia's register ([Nadia's design doctrine](../../apps/nadia/docs/design-doctrine.md#the-window)).

**Alveare** keeps the same arrangement in Buzz's components: places in the sidebar, Inbox first; the channel header names the channel and holds only the channel's own controls; the update card and the package's `AccountMenu` stand at the sidebar's foot; the top band keeps only the sidebar toggle.

### Page compositions

A product page is one of five page compositions, each answering one question first and each one component in `@nadicodeai/ui` that draws its order, variants and states from the content the page gives it. A page renders one of them and never assembles its own header, toolbar, table, rows, menus or column; `page-composition` refuses a page that renders a part a composition owns:

| Composition | Owner | First answer | Structure |
| --- | --- | --- | --- |
| Overview | `OverviewPage` | What needs attention? | The period, the figures, the chart cards, then the exceptions that need a person. Never a stack of previews of every section. |
| Collection | `CollectionPage` | Which record? | The title and the page's actions, the sibling collections as line tabs, one toolbar, then one line per record ("Tables and missing values"); one row is one hover and one open target. Actionable attention stands above the toolbar when present; business summaries belong to Dashboard and Analytics. A `rail` carries context intrinsic to the collection, such as the selected Library hub. The Portal's Agents are its exception, a `DataTable` of entity rows, as Vercel's Deployments list. |
| Object | `ObjectPage` | What is this, what can I do? | The parent in the path and its product’s section navigation, then the object card, then its sections, each a card only where it owns a decision. An Agent's first section, Profile, opens with her `AgentProfileCard`, the same in every product. |
| Settings | `SettingsPage` | What can I change? | Section cards, each saving its own fields; the danger zone closes the page. |
| Library | `LibraryShelf` for Agent templates; `CollectionPage` for Skills and Plugins | What reusable thing can I use? | Agent templates as Vercel's Templates gallery; Skills and Plugins as an entity collection, with staff's hub as its rail. |

Each composition's usage page holds its slots, geometry and states.

The pages share the parts below them, each with one owner: one line per record is `RecordList`, which `CollectionPage`, an overview's exceptions and an object's "Needs attention" section draw; an absence is `EmptyState` ("Elevation & Depth"); an overlay is the owner its job names ("Menus and commands"); what happened is `ActivityFeed` ("Activity"); a record's mark is `IdentityMark` ("Identity marks").

Every setting is that Settings composition, whether it belongs to the person, to the platform or to one record's Settings tab, so the column keeps its width and its field layout from one entry of a settings menu to the next, and each section saves its own fields from its own footer ("Cards and sections"). A card's header and fields share one uninterrupted body, and only its save footer has a divider. `SettingsPage`'s, `SettingsSection`'s and `SettingsField`'s usage pages hold the column, the cards and the fields; Nadia keeps its settings column at most 42rem. A structured table keeps visible column names while its container can hold the data, and a matrix whose every column must stay, such as notification audiences by group, keeps them all and lets its names wrap.

Every page opens with the same header, `PageHeader`: collections, working pages and settings pages alike, in the page's own column, so the title and the cards share one edge. The title is the page's name as its navigation writes it, the page's actions sit on the title's row through `PageActions`, whose usage pages hold their order, variants and wrapping, and under the title is one sentence saying what the page's concept is, as every Vercel page does (*Passport*: what it does and where it applies), with a link to learn more when there is one. An action always sits on the title of the thing it acts on, never on a tab's, and one command is a button, never a menu of one. Navigation remains a visible link or part of the navigation, never an overflow command. That sentence is the page's only explanation: nothing else introduces, explains or counts the page, and no line of text stands loose under a card or a list; what a card's reader needs is the card's own description or footer line, and a lasting condition is a notice above what it concerns. Inside an object worked from a section sidebar, the title is the section's name ("Apps"); a page that opens with the object's own header or card, such as an Agent's Profile, has it as its title and no title row above it.

A collection is named once, by the page's title, and the collection's card never adds a header that only describes it. A populated collection carries no picture; its first use shows one, in every product ("Elevation & Depth"). The table's footer holds only a count or the paging, never a help sentence: guidance a step needs lives in that step.

A section earns a card when it owns a decision, not for every label and value. A row links to its object and keeps secondary mutations in its menu ("Menus and commands"). A summary on an overview opens the same owner as its tab. Supporting detail collapses only behind a summary that keeps a count, state or consequence.

## Elevation & Depth

Product UI sits on one continuous workspace ground, `{colors.material-workspace}` (true black in dark), and solid objects rise above it at four heights: `nc-elevation-resting` for resting content, then `nc-elevation-raised`, `nc-elevation-floating` and `nc-elevation-modal` when an object is physically raised, floating or modal. Physical role chooses the height, never the importance of the content. Each recipe carries its own edge, so an elevated surface never adds a border, a ring or a second shadow.

The owner approved the materials: Vercel's surfaces with a little more depth on what floats, and glass only where it is useful. [The materials reference](../../docs/adr/2026-09-23-the-nadicode-design-system/depth-and-glass.html) shows each height and the glass in both modes. Four rules decide them.

**Content lies on the page.** Cards, KPIs, tables, lists and settings take the resting height: white in light and `{colors.dark-material-resting}` in dark, on the black page. Their edge is a 1 px line, black at 8% in light; in dark white at 14.5%, with a 1 px black ring outside it, so the edge stays sharp where one surface overlaps another. They have no grey fill, as Vercel's cards have none.

**What floats takes its depth from light, not from grey.** Raised (selected or dragged work), floating (menus, popovers, toasts, tooltips) and modal (dialogs, sheets, drawers, the command palette) surfaces stay white in light, and their shadows go slightly deeper than Vercel's at each height. Shadows stay black: a tinted shadow belongs only to a product view on a colour ground of the website. In dark they lift one barely visible step, `{colors.dark-material-raised}` for all three, and show their height with a line of light along the top inside, a white edge of 16 to 18% outside with the black ring beyond it, and a deep black shadow, all stronger with each height. The design-system checks fail a dark height darker than the one below it or lighter than that step (1.10:1 against the page), so a grey surface cannot ship; `{colors.dark-ink}` and `{colors.dark-muted}` keep the WCAG AA text floor on it. A field inside a surface is a darker well of it (that surface mixed 45% toward black) inside its edge, so every field stays visible; a footer or striped row is a quarter darker. Hover and selected fills step up from the surface they sit on. A component names only its height and its role (`bg-card`, `bg-popover`); `@nadicodeai/ui/globals.css` sets those roles to the height's tone on the element.

| Surface | Fill | Height | Use |
| --- | --- | --- | --- |
| Workspace | `{colors.material-workspace}` | None | The work area behind product objects. |
| Resting | `{colors.material-resting}` | `nc-elevation-resting` | Cards, KPIs, chart cards, tables, lists, settings. The default. |
| Raised | `{colors.material-raised}` | `nc-elevation-raised` | Selected or dragged work, an attached inspector. |
| Floating | `{colors.material-floating}` | `nc-elevation-floating` | Menus, context menus, popovers, selects, comboboxes, toasts, tooltips and hover cards. |
| Modal | `{colors.material-modal}` | `nc-elevation-modal` | Dialogs, sheets, drawers, the command palette. |
| Outlined | `{colors.material-solid}` | None; one `line` border | A flat panel: fact cells inside a record, the website's explanatory cards. |
| Plain | `{colors.material-solid}` | None | A structural card with no edge of its own. |

**Glass only where something moves under it.** Glass is a material, not a height: the resting surface's colour at `glass-opacity` over a backdrop blur (`glass-blur`) at `glass-saturation`, so the colour passing under it comes through and its text stays readable, with a 5% monochrome grain that keeps the blurred colour from banding. Its edge is the glass recipe (`nc-glass`): a line of light along the top and a hairline edge. It goes only where content passes under it:

- a collection's toolbar over the rows that scroll under it (on a page whose rows never pass under it, the toolbar is the plain row, with no fill or edge of its own);
- Nadia's chat bar and her window's header band over her conversation, which scrolls under both;
- the website header as the page scrolls under it;
- a small card over Agent orbs or a shader object.

**No glass over a plain page.** Menus, context menus, popovers, selects, comboboxes, the command palette, toasts, dialogs, sheets and drawers are solid: over a plain white or black page glass has nothing to show and reads as a grey box. Content stays solid too: cards, KPIs, tables, chat and forms. Reduced transparency and browsers without backdrop-filter replace glass with the solid resting surface. Transparency never reduces the opacity of labels or controls.

**Only a tooltip has a stem.** A tooltip points at its trigger with a small triangle; no other floating surface (menu, popover, select, hover card, dialog) draws a pointer, so a surface that opens from a trigger is placed beside it with its offset alone.

**Product views on the website** preserve the real interface. Nadia uses captures of the running application, with the unchanged full-size image linked and capture provenance recorded in the repository, following the founder's 12 October correction of invented UI. Portal and Alveare may use inert scenes built from real components with fictional example data recorded in the repository. Never invent interface controls or frame a view in a device mockup. A product window takes the raised height on its ground; a card that overlaps it floats. Live components use dark mode in a black section; captures preserve the application's captured theme.

Absence follows its container, and only a first use has a picture. In every product, the Portal included, a collection's first use shows its section's picture, and every other kind of absence goes without one. A picture sits on a working screen only there: never in a populated collection, a row or any other card; whole-page moments keep theirs ("Entry screens"). A page or region absence is a resting card with its own edge, never a dashed boundary; an absence inside a card is unframed, because the card already owns the edge, and `CollectionPage` puts every absence it draws in its card. `EmptyState` is the only owner of an absence: nothing else draws one (`empty-state-owner`), its `scope` (`page`, `region` or `card`) sets that frame, and it has no presentation option, so every product draws each kind the same way. A composition takes its absences as props (`empty`, `noMatch`, `failed`), as a `DataTable` takes `empty`. Its kind names the cause, and `EmptyState`'s usage page holds what each kind draws: `firstUse` (a collection with no records yet: its section's picture from the approved set in `@nadicodeai/design-system/assets/empty`, which [the art direction](../../skills/design/art-direction/references/surfaces.md#the-empty-state-set) keeps, and the one action that adds the first record, which the page's title row also keeps as its filled primary; a create action carries the leading +), `nothingYet`, `noResults`, `cleared`, `denied` and `failed`; a card-sized failure is `failed` with `scope="card"`, and there is no `ErrorCard`.

A persistent notice sits in the section it concerns, action feedback is a toast, and field validation stays with its field. A notice has no close button: it stays until the state changes, and one note stands for one concept. A section notice is `MessageNotice`; a condition of the page's own object that the page itself resolves, such as a past-due subscription on its billing page, is a `NoticeBanner` carrying that fix; a failure the person must handle is never a toast alone but a very short toast beside a persistent notice. A condition of the system, the setup or the staff (a connection, a stored secret, a failing job, anything a staff member fixes in settings) is a notification in the product's inbox (in the Portal, the bell beside the account at the sidebar's foot), as Vercel keeps certificate, domain and integration alerts in its dashboard's notifications popover ([Vercel's notifications](https://vercel.com/docs/notifications)). It is never a banner on the pages, the Dashboard or the frame they share; the page that fixes it shows the state in its own section. The frame's one strip marks the Portal's Impersonation, because everything done during it is recorded as the Customer's member. A notice, a failed read, a confirmation's error and a toast each show a message its product's catalogue worded, never a sentence written where it shows ([the decision](../../docs/adr/2026-10-08-every-message-a-customer-reads-is-a-code-its-products-catalogue-words.md)). Each wraps a long word, never truncates, closes its title with a full stop before a body that follows on the same line, and shows a body only when it says more than the title; their usage pages hold how each draws it.

Dialogs, sheets, drawers and the command palette sit over the scrim ("Colors"); popovers, selects and toasts float without one. Focus is not elevation: it is always the focus outline.

## Shapes

A corner follows what the thing is, on one scale: buttons, inputs, badges, tabs and menu rows take `{rounded.sm}` (6 px), a large (40 px) control takes `{rounded.md}` (8 px), and the 24 px icon button, a keyboard key and a breadcrumb link take `{rounded.xs-plus}` (4 px); a line tab takes `{rounded.none}`, standing on its underline. A surface takes its corner with its material: resting and plain cards and tables take `{rounded.sm}` (6 px); a raised card and any medium or large surface take `{rounded.lg}` (12 px), as do menus, popovers, dialogs, sheets and toasts, and a drawer on each edge not flush with the window; tooltips take `{rounded.sm}` (6 px); a surface that fills the window takes `{rounded.xl}` (16 px); a collection's glass toolbar takes `{rounded.lg}` (12 px); a conversation bubble takes `{rounded.2xl}` (20 px); Nadia's chat bar takes `{rounded.3xl}` (24 px). A checkbox mark is a square at `{rounded.xs-plus}` (4 px), still distinct from a circular radio. A segmented control's track takes `{rounded.sm}` (6 px), `{rounded.md}` (8 px) at 40 px, and its chosen option `{rounded.xs-plus}` (4 px), `{rounded.sm}` at 32 px. A slider thumb is a 6 by 14 px bar at `{rounded.xs}`. A corner is never chosen at the call site: it comes with the surface or the control. A corner nested inside another takes the outer radius minus the gap between them. Full-bleed sections take `{rounded.none}`. `{rounded.full}` is for avatars, switch and radio marks, filter chips, tags, the conversation's send button, and pill and circle buttons (show more, a banner's call to action, a scroller's buttons, the theme switcher). The other radii are for inner marks and geometry a component owns, such as a progress track.

## Motion

UI text stays static: no shimmer, typing, scrambling, pulsing or animated dots. Status labels use sentence case without trailing ellipses; complete instructional sentences keep their punctuation.

Only Agents move on their own. The interface answers a person's action, briefly, and stops: motion follows the action, grows from the element that was pressed, and settles, within 300 ms; sheets and drawers that follow the pointer take their own durations. Keyboard actions and anything used a hundred times a day are instant. While a person waits for something they asked for, one waiting line moves and nothing else ("System activity"). On the website three things move: Agents, the product at work, and the shader objects. A product scene runs the real components through their states in beats while it is half in view, rests on the finished work, loops, stops off-screen, and holds the finished work under reduced motion; a part of a scene arrives with `.nc-arrive`: it holds its place from the start and comes into focus there on the message-arrival timing, so a playing scene never shifts the page. The threads, the hive and the card's foil move at their own slow pace; each stops off-screen and holds one frame under reduced motion. Words never move, and there is no parallax, scroll reveal of text, marquee or shimmer. Ruled by the founder: a Stripe-grade product site, with shaders adjusted to the art direction and the products shown working.

The homepage figures have one owner-requested exception: a slight lean in response to a fine pointer, with no autonomous loop and no movement under reduced motion. Architecture and words stay still.

In the products a picture stands only on a collection's first use and on a whole-page moment: the entry screens, the failure pages, the Downloads page and a product's first run ("Entry screens"). Every picture stays still, with one exception: a first use's picture arrives once, after it loads. It holds its place and comes into focus there, from transparent and an 8 px blur to sharp, over `arrive` on `emphasized`, and never moves again; it never loops, and under reduced motion it does not animate at all and simply appears. Ruled by the owner.

The default transition in every product is `confirm`, 180 ms, `out-strong`. Under reduced motion, movement stops and paint continues: transforms and keyframed movement go to their end state, and opacity and colour transitions stay, because a fade or a colour change is the state change itself. A component never answers reduced motion by turning every transition off.

| Token | Use |
| --- | --- |
| `control` 100 ms | Hover and press on a control in a dense surface |
| `close` 140 ms, `open` 220 ms | Floating chrome and overlays leaving and arriving |
| `confirm` 180 ms | Confirmation, instant state flips, the default transition |
| `msg-in` 240 ms | A message arriving in a conversation |
| `transition` 240 ms | Property transitions on a working surface, and the Portal's one page transition, which carries an Agent's orb from a list row into its page |
| `arrive` 300 ms | A first use's picture coming into focus, once |
| `drawer-swipe` 400 ms, `drawer` 450 ms | Sheets, and the phone drawer that follows the finger |
| `wait` 1.6 s | Every waiting indicator: the waiting line, on every screen, a kept Hermes screen's included |

The easings are `emphasized`, `out-strong`, `in-out-strong` and `drawer`; the waiting line alone travels at constant speed (`linear`). A CSS transition or animation takes its duration and easing from these tokens; the design system's own stylesheets are held to it by a guard.

## Components

Every product builds from `@nadicodeai/ui`: stock shadcn components on Base UI, the chart kit, the assistant-ui conversation interface, and the compositions below. A component styles itself only through the roles and tokens in this file; it carries no raw colour, no `dark:` utility and no theme of its own. A page arranges and sizes a component and never restyles it: a new appearance is a typed variant or slot added to the component, and a missing reusable composition is added to the shared package, even while one product alone needs it. Product-specific data and persistence stay in the app adapter. Visible controls always come from the package.

### Information shapes

Choose the presentation from the answer the data gives. A change of page or
viewer scope changes the records and available actions, while the same kind of
answer keeps its component, density, value format and action placement.

| Information | Component and context |
| --- | --- |
| Records a person finds one of and acts on | `RecordList` inside `CollectionPage`, on a page of its own. |
| Records with the same comparable facts | `DataTable`. |
| A descriptive item with supporting prose | `ItemGroup` and `Item`, rather than invented table columns. |
| What happened, in order | `ActivityFeed` ("Activity"). |
| The identity of a record in a collection | `RecordIdentity`; the containing list owns navigation. |
| Facts about one object | `ObjectPage`'s facts, `ObjectFacts` or `SettingsFactsSection`, in one coherent section. A relation uses its same identity or `Tag`; it does not become a separate card. |
| A coherent set of changes | `SettingsSection` with its `save` and `SettingsField`. |
| A classification, lifecycle or operation | `Badge`, `StatusBadge` or `ActionStatus`. A missing value, an empty list and a failed read remain distinct. |
| Another view or sibling collection of the current scope | `TabsNav` or line `Tabs` ("Controls"). A tab never introduces a new scope or a second collection owner. |
| A measurement over time or across objects | The KPI or chart composition for the reading ("Data Visualization"); a named comparison is a `DataTable` with units and visible headings. |

One section holds one complete task or answer. A status, explanatory sentence
or Change button belongs beside the facts or fields it qualifies; it does not
earn a second section by itself.

### Component selection and usage

Every component has a usage page, as Geist documents each of its own: what it is, and five fields.

| Field | Says |
| --- | --- |
| When | The job it is for, with the products' own examples. |
| Instead | What to use for the jobs next to it. |
| Behaviour | Its states, what it does on each input, and what it owns. |
| Words | Which strings the product words, and how. |
| Access | Its role, its name, its keyboard path and its focus. |

The page is written in the component's own source, as the TSDoc of its main export (`@family`, `@when`, `@instead`, `@behaviour`, `@words`, `@access`), so an editor shows it on hover in every product; `@nadicodeai/ui`'s showcase renders every page, family by family, with the component drawn in its states. A component without its usage page, or a page without its specimen, fails the package's checks. A usage page holds its component's own rules, applies the shared rules in this file and never restates one.

The table below maps all 73 entries in the [Geist component reference](https://vercel.com/geist/introduction) to our shared owners; how each owner is used is on its usage page. **Component** means a public owner exists for the job; it does not promise every Geist prop. **Composition** means existing parts provide the presentation together. An **enhanced capability gap** names specific behavior, not the absence of a matching component name. Use our exported APIs and names, not Geist's prop names: for example, our Button appearance is `variant`, while HTML submission behavior is `type`. A dedicated name in Geist does not require another primitive in our library; prefer existing compositions before adding another registry or dependency.

Use the [complete compositions](#complete-compositions) before assembling primitives. Component mechanics and supported props live in [the shared UI source](../ui/src/components); product adapters supply data, routes, translations, permissions and persistence. A component's visual owner supplies its geometry, typography, colour, focus and surface zones. A caller supplies layout and content.

Every example and consuming surface covers its reachable populated, empty, loading, invalid, disabled, failed and compact states. A failed request is not empty data; a disabled control has a reason. Controls keep their visible names, keyboard behavior and focus return. Keep product wording in sentence case. A source reference establishes a pattern, while rendered examples establish what the implementation actually looks like.

| Geist reference | Shared owner |
| --- | --- |
| [Address Input](https://vercel.com/geist/address-input) | `Field` parts in a `FieldGroup` 16 px apart: a `NativeSelect` country first, a street `Combobox` (or `Input` when there is no lookup), Address line 2 with (optional) written in its label, then pairs such as State and ZIP or Postal code and City two-up; a failed lookup ends in the manual fields. No dedicated export: it needs a provider only a product has |
| [Avatar](https://vercel.com/geist/avatar) | The `Avatar` family; a record's mark is `IdentityMark`, and an Agent is her `AgentOrb` |
| [Badge](https://vercel.com/geist/badge) | `Badge`; `StatusBadge` for state, `Tag` for a categorical fact |
| [Banner](https://vercel.com/geist/banner) | `NoticeBanner` |
| [Book](https://vercel.com/geist/book) | `Card` + `AspectRatio` and cover artwork; no book-frame export |
| [Breadcrumbs](https://vercel.com/geist/breadcrumbs) | The `Breadcrumb` family |
| [Browser](https://vercel.com/geist/browser) | `Card` + `AspectRatio` and decorative browser chrome; `DesktopShell` stays real product chrome |
| [Button](https://vercel.com/geist/button) | `Button`, `ButtonLink` and `buttonVariants` |
| [Calendar](https://vercel.com/geist/calendar) | `Calendar`; `DateRangeControl` for a metric's window |
| [Checkbox](https://vercel.com/geist/checkbox) | `Checkbox` with the Field parts |
| [Choicebox](https://vercel.com/geist/choicebox) | `ChoiceboxGroup` |
| [Clearable Input](https://vercel.com/geist/clearable-input) | `ClearableInput` |
| [Code](https://vercel.com/geist/code) | Semantic `code` with the shared mono typography roles |
| [Code Block](https://vercel.com/geist/code-block) | `CodeBlock` |
| [Collapse](https://vercel.com/geist/collapse) | `Accordion`, `Collapsible` and the complete `Disclosure`; a collapse never nests in another |
| [Combobox](https://vercel.com/geist/combobox) | `Combobox` |
| [Command Menu](https://vercel.com/geist/command-menu) | `CommandDialog` |
| [Context Card](https://vercel.com/geist/context-card) | `HoverCard` with `HoverCardSummary` |
| [Context Menu](https://vercel.com/geist/context-menu) | `ActionContextMenu` |
| [Copy Button](https://vercel.com/geist/copy-button) | `CopyButton`; `TextWithCopyButton` binds a shown value to it |
| [Description](https://vercel.com/geist/description) | `ObjectPage`'s `facts`, `ObjectFacts`, `SettingsFactsSection`, `TableValue` |
| [Destructive Action Modal](https://vercel.com/geist/destructive-action-modal) | `ConfirmDialog` |
| [Dots Menu](https://vercel.com/geist/dots-menu) | `ActionMenu` for a record, `PageActions`' `menu` for a page |
| [Drawer](https://vercel.com/geist/drawer) | `Drawer`, the phone's bottom sheet for a short job |
| [Empty State](https://vercel.com/geist/empty-state) | `EmptyState`, following the [absence contract](#elevation--depth) |
| [Entity](https://vercel.com/geist/entity) | `RecordList` for records; `Item`, `ItemGroup` and `ItemSelect` for a descriptive list; `CatalogEntry` in `LibraryShelf` and `CatalogDrawer`; `AgentListRow` |
| [Error](https://vercel.com/geist/error) | `FieldError` (a field), `MessageNotice` (a control or section), `EmptyState kind="failed"` with its reference (a block) |
| [Error Card](https://vercel.com/geist/error-card) | `EmptyState kind="failed" scope="card"`; no ErrorCard export |
| [Feedback](https://vercel.com/geist/feedback) | `Popover`, `Field`, `Textarea`, `RadioGroup`, optional `Select`, and `Button` |
| [Fieldset](https://vercel.com/geist/fieldset) | `SettingsSection` with `SettingsField`; `FieldSet` and `FieldLegend` for semantic subgroups |
| [File Tree](https://vercel.com/geist/file-tree) | `SidebarMenu` + `Collapsible` + `FileTypeIcon` can follow sidebar-11 for nested files |
| [Gauge](https://vercel.com/geist/gauge) | `Gauge` |
| [Grid](https://vercel.com/geist/grid) | Layout grid, `CardRow`, website `nc-page`; no ruled `Grid` API |
| [Input](https://vercel.com/geist/input) | `Input`; `InputGroup` for parts inside the box; `Field` or `SettingsField` for the card around it |
| [JSON View](https://vercel.com/geist/json-view) | Composition for static JSON: code display with exact serialized text |
| [Keyboard Input](https://vercel.com/geist/keyboard-input) | `Kbd` |
| [Label](https://vercel.com/geist/label) | `Label`, `FieldLabel` and `FieldLegend` |
| [Load More Button](https://vercel.com/geist/load-more-button) | `LoadMoreButton` |
| [Loading Dots](https://vercel.com/geist/loading-dots) | `Button`'s `loading` and `WaitingLine`; no LoadingDots |
| [Menu](https://vercel.com/geist/menu) | `ActionMenu`, `ActionContextMenu`, `PageActions`' `menu` and `AccountMenu`; the `DropdownMenu` family directly only for a choice menu such as a sort |
| [MiddleTruncate](https://vercel.com/geist/middle-truncate) | `MiddleTruncate` |
| [Modal](https://vercel.com/geist/modal) | The `Dialog` family, `FormDialog` for a short form and `ConfirmDialog` for a consequence |
| [Multi Select](https://vercel.com/geist/multi-select) | `MultiSelect`; `Combobox multiple` when typing to find matters more; `CollectionFilters` for query facets |
| [Note](https://vercel.com/geist/note) | `MessageNotice placement="section"` |
| [Pagination](https://vercel.com/geist/pagination) | Not built until a page needs it: the pages of a table of records are `DataTable`'s own Previous and Next under its card, and `RecordList`'s footer paging |
| [Phone](https://vercel.com/geist/phone) | `AspectRatio` + decorative frame and captured content; no device-frame export |
| [Pill](https://vercel.com/geist/badge#pill) | `Badge` through `render` for a link-like badge; `Tag` for a static category; `CollectionFilters` for filters |
| [Progress](https://vercel.com/geist/progress) | The `Progress` family; `UsageMeter` |
| [Project Banner](https://vercel.com/geist/project-banner) | `NoticeBanner` |
| [Radio](https://vercel.com/geist/radio) | `RadioGroup` |
| [Relative Time Card](https://vercel.com/geist/relative-time-card) | `RecordTime` |
| [Scroller](https://vercel.com/geist/scroller) | `ScrollArea` and `ScrollBar` |
| [Search Input](https://vercel.com/geist/search-input) | `CollectionSearch` in `CollectionToolbar` |
| [Select](https://vercel.com/geist/select) | The `Select` and `NativeSelect` families |
| [Separator](https://vercel.com/geist/separator) | `Separator` |
| [Sheet](https://vercel.com/geist/sheet) | `Sheet`; `RecordSheet` for one record looked at from a list; `FormDrawer` and `CatalogDrawer` for its steps and catalogue forms |
| [Show more](https://vercel.com/geist/show-more) | Not built until a page needs it: a list that grows from the server takes `LoadMoreButton` |
| [Skeleton](https://vercel.com/geist/skeleton) | `Skeleton` and `SkeletonRegion` |
| [Slider](https://vercel.com/geist/slider) | `Slider` |
| [Snippet](https://vercel.com/geist/snippet) | `Snippet` |
| [Spinner](https://vercel.com/geist/spinner) | `Button`'s `loading` and `WaitingLine`, no Spinner export |
| [Split Button](https://vercel.com/geist/split-button) | Not built until a page needs it |
| [Status Dot](https://vercel.com/geist/status-dot) | `StatusBadge`, whose tone is a shape of dots, never a single dot |
| [Switch](https://vercel.com/geist/switch) | `SegmentedControl` for two or three exclusive views of one surface; `Tabs` for panels; `ToggleGroup` for pressed buttons; `ThemeModeSwitcher` for the theme |
| [Table](https://vercel.com/geist/table) | `Table`; `DataTable` for records; `RecordList` for rows that name themselves; `TableCard` and `TableValue` |
| [Tabs](https://vercel.com/geist/tabs) | `Tabs`, and `TabsNav` with `TabsLink` for URL views |
| [Text With Copy Button](https://vercel.com/geist/text-with-copy-button) | `TextWithCopyButton` |
| [Textarea](https://vercel.com/geist/textarea) | `Textarea` with `Field` or `SettingsField` |
| [Theme Switcher](https://vercel.com/geist/theme-switcher) | `ThemeModeSwitcher`, `ThemeModeMenu` and `ThemeModeSegments` |
| [Toast](https://vercel.com/geist/toast) | `Toaster` and `showMessageToast` |
| [Toggle](https://vercel.com/geist/toggle) | `Switch` for a boolean setting; our `Toggle` is a pressed button |
| [Tooltip](https://vercel.com/geist/tooltip) | The `Tooltip` family |
| [Video](https://vercel.com/geist/video) | `AspectRatio` + native video, controls, poster and caption tracks |

### Additional shared building blocks

These shared components also follow the same foundations, although Geist does not list each as a separate component. Use the existing product composition before choosing one directly.

| Owner | Use and composition contract |
| --- | --- |
| `Sidebar`, `SidebarSections`, `UpdateCard`, `AccountMenu`, `DesktopShell`; the Portal's `PortalShellFrame` | Persistent product navigation and workspace chrome ("Product shells"). The product owns destinations and active state; the shell owns compact behavior, landmarks and layout. `SidebarSections` is the sidebar's swap into an object's sections or an area's sub-menu, `UpdateCard` the offer to update above the account. Never add a competing sidebar to a page. |
| Chart kit and `KpiCard` | Choose the reading through [Data Visualization](#data-visualization), then use its shared chart owner with units, period, legend and accessible data. Geometry never decides business thresholds. |

`Popover`, `InputOTP`, the resizable panels, `AspectRatio`, the Field parts, `PairingCode`, `QrCode` and `FileDiff` each hold their contract on their usage page. Which form surface a job takes is decided in [Menus and commands](#menus-and-commands).

### Complete compositions

Choose the owner for the whole task. Keep its slots and state handling together so pages supply content without recreating card zones, toolbar geometry or overlay behavior.

| Task | Shared composition |
| --- | --- |
| Settings | `SettingsPage` → `SettingsSection` with its `save` → `SettingsField`. |
| Read-only facts | `ObjectPage`'s `facts`, `SettingsFactsSection` or `ObjectFacts`, with `TableValue` for missing facts. |
| Entity collection | `CollectionPage` on `RecordList`, with `RowEditForm` for the edit inside a row. The page supplies its records and their commands as data, never its own toolbar, table, rows or menus. |
| A list inside another page | `RecordList` with its `heading`: an overview's exceptions, an object's "Needs attention". |
| Comparable collection | `DataTable` within `TableCard`, under `PageActions` and `CollectionToolbar`. |
| Overview | `OverviewPage` with its `KpiCard` figures, its `ChartCard`s and its exceptions; the product supplies the reading time and the way to each figure's owner. |
| Object | `ObjectPage` with `ObjectSection`, `ObjectFacts`, a `RecordList` with its heading, a `SettingsSection` or an `ActivityFeed` as its sections. |
| A reusable thing to add | `LibraryShelf` with `AgentCard`s for a gallery of templates; `CollectionPage` for a catalog a person finds, opens and adds from (the Portal's Skills and Plugins); `CatalogDrawer` with `CatalogEntry`s where the choice opens from a page. |
| What happened | `ActivityFeed`; one run's steps are `RunTimeline` ("Activity"). |
| Getting something set up | `NumberedSteps` for the steps on a page; `FormDrawer job="steps"` for a setup with a wait. |
| Short form: a new record, or one or two values from a command | `FormDialog` with Field parts; Portal binds it through `ActionDialogForm`. A record in a list edits inside its row (Entity collection); a catalog uses `CatalogDrawer` and a setup in steps `FormDrawer` ([Menus and commands](#menus-and-commands)). |
| One record looked at from a list | `RecordSheet`. |
| Destructive confirmation | `ConfirmDialog`; the app's confirm-action adapter. |
| Global commands or record actions | `CommandDialog` for Find; `ActionMenu` and `ActionContextMenu` for one record; `PageActions` for a page. |
| Absence or failed read | `EmptyState`, through the composition's `empty`, `noMatch` and `failed` or `DataTable.empty`; `MessageNotice` for persistent conditions. |
| Rich choices | `ChoiceboxGroup` with `ChoiceboxGroupItem`, in the group's own `FieldSet` and legend. |
| Copyable identifier or command | `TextWithCopyButton` for a value, `Snippet` for a command; the prompt is the component's and is never copied. |
| Plain code or static JSON | `CodeBlock`. |
| A record's time | `RecordTime`: the time in its verb's sentence, or the clock time in a feed, with UTC and the local zone in its hover card; the product supplies its refresh policy. |
| Expandable file navigation | `SidebarMenu` and `Collapsible`, following shadcn sidebar-11, with file icons and real file links or commands. Keep the nested lists semantic; selection and roving arrow-key navigation, a full tree widget, need a separately verified keyboard contract. |
| Feedback collection | `Popover` with Field, Textarea, optional RadioGroup/Select and Button; inline rendering uses the same form parts. The product supplies the prompt, the topic choices, the permitted context and the destination; no collection backend is implied. |
| Native media playback | `AspectRatio` around a native video with its controls, poster and caption tracks. The product supplies the source, the captions, a transcript where needed, the playback policy and the recovery when the media is unavailable; a richer player needs a concrete media requirement. |

Use `CopyButton`, `TextWithCopyButton`, `Snippet` and `CodeBlock` for their complete compositions. Feedback and expandable file navigation combine existing parts with product adapters. Syntax colour, full JSON or tree-widget navigation, and a JSON view's search highlighting and depth controls remain behaviour gaps: do not invent undocumented props or copy a local implementation into every caller.

### Personalization boundaries

The owner's line: follow Geist for everything that makes components and compositions coherent (the spacing scale, paddings and gaps, control, row and icon sizes, the type scale and hierarchy, layout, density, alignment, corner radii, how components are built and composed, and their behaviour and rules), and keep nadicode's colour, glass and material (surfaces, depth, shadows, blur), Agent orbs, genuine provider marks, categorical Library colours and sentence-case product words. Every part, option and rule comes from Geist's page or is already ours; nothing is invented. These differences are tokenized or owned by a typed component variant. Business status, permissions, transaction boundaries and product copy remain the product's truth; a visual reference cannot change them.

The owner's decisions where Geist and ours differ:

- Words stay in sentence case, never Geist's Title Case: tab titles, collapse headings, reasons and a date range's presets ("Past 7 days") included.
- Our component names stay; a Geist part we lack is added under a name that says what it is.
- A settings toggle, the theme included, is a draft that its section's Save applies, where Geist applies a toggle on change, as Vercel's own settings cards do with their footer Save.
- Find goes to places only: it runs no command and has no page stack.
- A wait stays still, with no spinner, loading dots or shimmer; a `Button` takes Geist's `loading` behaviour with its pending word in place of the spinner.
- `ChoiceboxGroup`, `ClearableInput`, `Snippet` and `LoadMoreButton` are components, never recipes a page assembles.
- The three-dot trigger stays 36 px, ruled by the owner.

### Controls

`Button`'s usage page holds its sizes, variants and loading; `textLinkVariants`' holds the link treatments; `Checkbox`'s and `RadioGroup`'s hold their checked marks.

- **Dialog footers.** A dialog's Cancel sits at the footer's leading edge and its primary at the trailing edge; a dialog with one button fills the footer with it. A step back (Previous) is grouped with Cancel in one wrapper at the leading edge, so the footer keeps two ends. A choice to stop being asked is a checkbox at the end of the body, remembered by whichever action is taken, never a third button. On a phone the dialog and the confirmation are bottom sheets whose footer keeps its one row, Cancel leading and the primary trailing.
- **Tabs** switch between sibling collections or views of one scope, such as a company's Members, Invitations and Teams, or Billing's Subscriptions, Approvals and Stripe events. One shows at a time; the choice lives in the URL, as a route or `?tab=` with the first tab left out, so it survives a reload, Back and a shared link; and switching never changes whose things you are looking at. A tab never introduces a new scope: another company or Agent is another place, reached from the sidebar or a scope selector, and a choice of whose things are listed is a filter in the toolbar. A page's collections take the line tabs (`Tabs` with `TabsList variant="line"`), under the title row and above the toolbar. The page's actions stay on its title row whichever tab is shown, the toolbar belongs to the tab shown and switching clears its search and page, and two lists are never stacked with a tab row between them. A set of different things, such as Nadia's company sections, is pages in the sidebar, never tabs. A choice that changes a value rather than a view uses radio or toggle semantics instead. `Tabs`' usage page holds the tabs' geometry and words.
- **Fields.** Input, Textarea, Select, NativeSelect, Combobox, MultiSelect and InputGroup share one box in three heights, `Input`'s, and `Field`'s label and message: their usage pages hold the geometry, the states and the validation. Help is the one-line description of the card that holds the field, one setting to a card, never a line under each field; a field that may stay empty says so in its label, never in its placeholder.
- **Focus** is drawn once, by the base layer, on every `:focus-visible` element. A component never repaints it; it may move it to a proxy that stands for the focused element, as a field group does for its input.
- Enabled buttons show a pointer; a disabled control advertises nothing. On coarse or non-hover input every target is at least `{spacing.touch-target}`, while a checkbox or radio mark stays compact and its label supplies the target. An unchecked checkbox or radio mark is its `{colors.input}` edge with no fill of its own, so it shows the surface it sits on, and greys on hover unless disabled.

### Cards and sections

A card or settings section is composed around a real task, collection or form; its header names the content and may explain scope or consequence, and it is never a card holding only an introduction. A card's or a section's title carries no icon, unless the card stands for one external system, whose mark it then carries. On a settings page the shape follows the data. Settings that one card's description covers share that card, whatever their controls, as Vercel's Root Directory card holds its switches, and `SettingsRows`' usage page holds their rows; a typed value, a name or an address, keeps a card of its own. Every choice on a settings card, a switch, a theme, a language or a pick from a short list among them, is a draft until the card's footer Save applies it, and the product's toast confirms it; a save that fails says so under the card and keeps the draft. A collection is a table whose Add opens a form and whose every row carries **⋯**, and a destructive action there confirms first. A status is a read-only card with the one action that fixes it. A row of five figures sits in one row from 1280 px, three over two below that, two by two with the fifth across on a tablet, and one column on a phone; it never leaves one figure alone beside a hole.

`Card`, `CardRow`, `SettingsSection`, `SettingsFactsSection` and `KpiCard` hold their own shapes on their usage pages.

### Menus and commands

A record's primary destination is its visible name or a labelled link. Navigation is never hidden in its action menu. Keep the main task visible: Edit opens the record inside its row, as in Vercel Domains; Connect belongs beside an available connection. Additional mutations use an end-aligned `ActionMenu`, the same commands on a right-click through `ActionContextMenu`; a menu is not required when the row has no secondary mutations. Commands reach a person only through `PageActions` (a page), `ActionMenu` and `ActionContextMenu` (a record) and `AccountMenu` (the account), each taking its commands as data (`menu-owner`). A menu names the record it acts on, keeps a command that cannot run in place with its reason, and puts the destructive command last after a divider; `ActionMenu`'s usage page holds how it draws them, and `AccountMenu`'s the account menu's one exception. A destructive command never becomes the loudest repeated action: it is never a button on every row.

Choose the surface by where the thing being changed is shown and how big the job is, never by the page that happens to need it: the same job takes the same surface in every product. Adding a skill is the catalogue drawer in the Portal and in Nadia, and editing a task or an MCP server opens its row in both. Each surface has one owner:

| The person wants to | Surface | Owner | Example |
| --- | --- | --- | --- |
| Change a record shown in a list | Its row opens into the form | `RowEditForm` in `CollectionPage`'s `editor` | Vercel Domains › Edit; a task, a mailbox, an MCP server, a channel |
| Change a fact on a record's own page or card | In place: a pencil turns the fact into its field, saved by the card's Save | `AgentProfileCard`; an `ObjectSection` that swaps to its `SettingsSection` | An Agent's name and description |
| Add a record through a few fields, or change one or two values from a command | A centred dialog | `FormDialog`; the Portal's `ActionDialogForm` | New task, add a mailbox, add an MCP server |
| Choose from a catalogue | A modal sheet at the right edge | `CatalogDrawer` | Add a skill, in both products; Vercel Domains › Add |
| Add one of a few kinds of record | A menu from the title row's primary button; a kind that needs its own fields opens its dialog | `PageCommand`'s `choices` | Vercel Add New…; the Wiki's Add source |
| Set up in several steps with a wait | The same sheet, with its progress | `FormDrawer job="steps"` | Connect a messaging channel; Telegram's QR setup |
| Look at one record from a list, then go back to the list | A sheet from the right that leaves the page usable | `RecordSheet` | One Stripe event in Billing |
| Do a workflow a person could link to | Its own page | The page's composition | Vercel Add New › Project; New Agent |
| Confirm a consequence | A confirmation | `ConfirmDialog` | Remove, Disconnect |

A record already on screen is never edited in a dialog or a sheet, and a sheet is never chosen because an action takes input: a catalogue is `CatalogDrawer` and a setup in steps is `FormDrawer` with `job="steps"`. A `Drawer` is only the phone's bottom sheet for a short job, never a desktop surface and never a confirmation. A settings page is the Settings composition. These choices follow the live Vercel Domains, Projects and settings pages and the [Geist Modal](https://vercel.com/geist/modal), [Sheet](https://vercel.com/geist/sheet) and [Fieldset](https://vercel.com/geist/fieldset) patterns.

Collection search and filters stay immediately above their rows. The title row holds the collection's primary action, unless an inline form already exposes that task. Scope selectors preserve the authorized organization or account. Filters do not compete with primary actions.

### Tables and missing values

`Table`, `DataTable`, `TableCard`, `RecordList`, `Item` and `Tag` hold their geometry, columns and rows on their usage pages; `page-composition` refuses a `DataTable` without column headings. No heading sorts the rows, so no heading is a button: the page holds the order, through a `CollectionSort` above the list, and `aria-sort` marks the column the rows follow.

A collection fits its container without sideways scrolling, and on a phone a row keeps its name above supporting facts that wrap in full, never a stack of labelled values, where a status keeps its own mark; `DataTable`'s and `RecordList`'s usage pages hold how their columns give way. Text anywhere in the product breaks between words, and inside a word only when that word alone cannot fit a line; a machine string such as a hash or a key breaks at any character in wrapped text, or is cut in the middle with `MiddleTruncate` on one line.

An unknown or not-applicable value in a table, a comparison list or a set of facts is a muted `—` with screen-reader text, in the same slot as a present value; zero stays zero. An absent report is not `None`: `None` is only for a complete report that found nothing. A stale value keeps its last reading with a short `Stale` qualifier. Loading, failure, missing permission and actionable setup each keep their own visible state and never turn into a dash. Essential reasons and actions never live only in a hover tooltip.

### Status

A status is one `StatusBadge`: a shape and a word in one of five tones, with no pill around them, as Vercel sets a deployment's state beside its time. Each tone has its own shape, filled with dots on a honeycomb grid, the material of the Agent orbs: green a circle, blue a diamond, amber a triangle, red a square, and gray an empty ring, because gray is every state with nothing to act on, Ready and Archived as much as Paused. The shape says the tone, so a status reads without colour, and the word says the exact state; a new state picks a tone, never a new shape. Ruled by the owner: the stock tinted pill read as generic, and the status takes the Agents' own dots. `StatusBadge`'s usage page holds the tones; `ProgressChecklist`'s holds how far a record is through the steps that make it ready.

The Portal, Nadia and Alveare draw every status with the one `StatusBadge` in `@nadicodeai/ui`, which owns the tones and their shapes; each app keeps only its value-to-tone map in one module, and nothing else chooses a status colour. A status never moves. A single coloured dot is never a status, including beside a visible label and inside an Agent row or filter; the shared package exports no status-dot component. An Agent's orb remains her identity and activity indicator.

In a list or a feed a status shows only for the exception: the record or the event that differs from the others, such as a failure, a revocation or a wait for a person's decision. The usual state shows nothing, so a status never stands on every row. An object's own status sits beside its name in the path ("Product shells").

An Agent fleet's operational-state column and a run history's outcome are facts being compared, so each entry keeps its named `StatusBadge`, including Ready and Completed. This exception never permits a single coloured dot.

### Activity

What happened has one shape in every product, `ActivityFeed`, whose usage page holds its row and frame; one run's steps are `RunTimeline`'s.

### System activity

Who does the work decides how it looks. An Agent's work is that Agent's orb in its state and a sentence saying what it does. The system's own work never takes an orb; inside a conversation there is no system's work, only hers and the person's ("Agent orbs"):

| The wait | What shows |
| --- | --- |
| Under about a second | The control's own label changes to its pending word, a button's through its `loading` and `loadingLabel`; its place, size and icon stay. |
| Past a second | One 2 px waiting line of ink beside the work, a 32% segment crossing it on `wait`, and nothing else moves. |
| Measurable | A progress line with a written count, "Step 2 of 3" or "12 of 40 MB". Never a percentage. A ratio of a fixed maximum is a `Gauge`, which prints the bare number and leaves the unit to its label. |
| A page load | Still skeleton placeholders in the shape of the content, inside one `SkeletonRegion`, never pulsing and never paired with a waiting line. |

One wait gets one indicator. A request waiting for a person is not a wait: queued, paused, blocked, failed and done take the status or feedback treatment. A wait is ink; info blue never marks one.

A wait shows where the thing lives. A drawer or dialog stays open only for the person's part: what they still have to do in it (scan a code, finish in the browser, type) or a check that can still refuse what they entered, so they fix it there. Once that is accepted it closes, and what the system still has to do (a restart, an install, a sync) shows on the record's own row or section: its waiting line with the work named ("Connecting Telegram"), then its state, or its failure with the next action. An action that is itself the wait, such as sending an invitation or a payment, keeps its dialog until it answers. Ruled by the owner: Telegram's connect drawer stayed locked on "Connecting" while messaging restarted, after the person's part was done.

In a row the wait is the row's detail line naming the work, with one waiting line under it and no status badge for the same wait. A waiting line beside a visible sentence or label that names the work is `decorative`; a line standing alone takes a `label`.

### Failure surfaces

A page that cannot show what was asked for opens with a picture, the way a Portal email does: one whole cut-out in art direction's editorial register, centred above the words, whose idea says what happened. Decorative artwork has no gallery caption, title, materials or year. Below the picture, on the same centre line, stand a mono eyebrow naming the situation, the page's one heading, one plain sentence, and the recovery action, which leads. The cut-out's transparent ground takes the page's own, so one file serves light and dark, and it stays whole at every width: it scales down with the column and is never cropped by the viewport. No orb turns on a failure page.

A Portal email, whether the Portal or Knock sends it, follows the same order under its picture, in one structure: the picture, the eyebrow and a short heading keep the centre line; one sentence of consequence, a list between hairlines when the email is about several things (each a semibold heading over its one sentence), a table of its facts between hairlines when it has facts, one to-do sentence, the fallback link and the footer stand flush left; the button stays centred.

The not-found page and the fault page are one composition, in that order: the picture, the eyebrow, the title, one sentence, then the recovery first. Only a fault carries its code, and no orb turns on either. Two registers share it:

- **An absence** is an address or a record that is not there. Its picture carries the joke of the missing address. Nothing went wrong, so nothing takes a feedback role, no glyph stands in for the failure and no code is printed.
- **A fault** is a request the product stopped. Its picture is calm and never a joke about lost work. Below the actions stands the evidence: the diagnostic code, the word that names it and one line saying what to do with it. The error pair marks the eyebrow.

An absence inside a product's own chrome, such as an address or a record the Portal cannot open, keeps its picture, centred in the content region with the chrome around it. A fault inside the chrome keeps the words alone. Identity hue reaches a product failure surface only through the picture. The words never apologize and never name the mechanism ("Product messages" in `product-design`). On the website a failure surface is an ordinary full-bleed section and takes a section ground like any other.

### Agent orbs

An Agent is its orb, in every product; there is no other Agent picture. `AgentOrb`'s usage page holds its forms, colours, sizes, states and reduced motion, and `AgentListRow`'s holds an Agent's row in Nadia's list. A surface that only knows a machine checked in draws idle.

- **In a conversation.** Her states are the conversation's own: the orb in its header follows them, and every mark of her activity in the thread is her own orb in her real state, never another Agent's figure, a spinner, a ticker or a pulse. Her steps and tool calls hold still and say in words what they do or did, with a tool's kind as a decorative icon beside the words and no state of its own; the outcome of a step or a request is the `StatusBadge`, and a failed turn is the `MessageNotice`. A conversation has two actors, her and the person, and every wait in it belongs to one of them. Everything she does, including what follows the person's approval or answer, is her orb in its state: working once Run is pressed, thinking once a question is answered, waiting while an approval or a question waits for the person, blocked when a turn fails, done once when she finishes. The person's own action in progress shows on what they made: a file stays veiled on its own chip until it is uploaded, and fills only where its upload reports progress; the microphone shows it records. A pressed control keeps its place and changes only its word, a button through its `loading`. The waiting line is for the window's own loading, outside the conversation. Ruled by the owner: the Agent's states are the chat's default states, not random icons, and the person never meets "the app" in a conversation, only the Agent.

### Agent card

An Agent shown as an object, in the Portal's Library and Nadia's gallery alike, is one `AgentCard`, whose usage page holds its shape.

### Company card

The company's card is `CompanyCard`, whose usage page holds where it appears (sign-up, the account's Profile, a Sign-up invitation, a colleague's invitation), its face, foil and motion.

### Identity marks

A company detail leads with its own published logo beside its name and description, never a deployment preview or an empty media panel. Every company logo keeps its original colours and proportions, contained whole on the company's dark ringed tile, which also backs transparent artwork; it is never converted to a silhouette or clipped to a shape. Until a logo is found, or when it does not load, the same tile carries the company's initials in white, so a found logo replaces only the letters. A company's mark never shows its standing and is the same in either directory: a Partner is said in words where its standing matters, never by a colour. `IdentityMark` owns the rendering and fallback; `ObjectPage` owns its placement.

An identity mark says which thing a record is, and `IdentityMark` is its one owner: a row's, an object's, a feed's and a sidebar's mark is one of its kinds, never a decorative icon. A list gets its richness from real identity marks, never from decoration. `IdentityMark`'s usage page holds its kinds and sizes.

A recognised app, platform or model maker is drawn with its genuine mark beside its readable name; an unknown one keeps its name and a text monogram, never a guessed logo. A model's maker is 24 px, in a model list, a routing table or a setting, and on the website's lists of apps and makers. A mark grows only where it stands for the object itself, as on its card or at the head of its page, and there every surface of that object draws it at one size. A mark identifies and never replaces the label, and its colour identifies the maker, never a connection's health. Marks are used nominatively, to name a platform, an integration or a provider, never next to wording that implies endorsement. A brand mark never stands in for a UI icon, and a UI icon never stands in for a brand; a catalogue icon is one of the catalogue's fixed set, never a brand mark, and an app keeps its brand mark. In a chart, a model is named by its label, and marks stay out of legends.

### Product views

Website product views follow "Product views on the website" in [Elevation & Depth](#elevation--depth): authentic captures or real-component scenes, with provenance recorded in the repository. Preserve the product's register and interface. Crop to the detail being explained; retain an original-image link for captures and never frame a view in a device.

### CSS components

These three ship as framework-neutral CSS; `@nadicodeai/ui` wraps the last two for React.

**`dotted-field`** — the ground of a canvas where a person or an Agent places and arranges objects: a diagram of an Agent's work, a board of its outputs. The dots say that things here can be moved, so the field never sits behind a dashboard, a list, settings or a website section, and it never stands in for elevation.

**`usage-meter`** — consumption against its allowance; `UsageMeter`'s usage page holds the rest.

**`run-timeline`** — the thread of an Agent's run, one step per row; `RunTimeline`'s usage page holds the rest.

## Iconography

Every product draws Lucide's shapes, unedited, in the house render: a 1.5 px line (`{spacing.icon-stroke}`) measured on screen at every size and 1.25 px (`{spacing.icon-stroke-sm}`) at 12 px, round caps and joins, no fill, and the text colour by default. Semantic icons may use their status or category tone with an adjacent readable label; navigation icons stay neutral. Sizes are 12, 16, 20 and 24 px, 16 by default, and inside a stock component the registry's own icon size stands; persistent navigation uses 20 so its entries read before the content beside them, and a collapsed rail keeps 16. A surface draws a small, disciplined set, never the whole library.

Three icons are the house's own, drawn on Lucide's grid in the same render: `arch` for the company and its doors, `approvals`, the arch with a check, for work waiting on a person, and `agent`, the orb seen face-on with the Agent as the dot on its path. They name those three things wherever an icon does, and no Lucide glyph stands in for them. An icon beside text is decorative; an icon-only control carries an accessible name.

`ObjectFactValue` gives Agent, Installation and Customer counts their fixed semantic mark and category colour inside the value: cobalto, acqua and viola respectively. A linked value keeps the same pairing. Its label stays plain, and a status remains `StatusBadge`; category colour never asserts health.

The blue category uses the cobalto 200 ground and cobalto 1000 ink through `category-blue` and `category-blue-foreground`, with the same ramp steps in each theme. It never uses the status roles `info` or `info-soft`.

An icon belongs to a control or a menu row, never to a title. A card's, a section's or a label's title and a KPI's label carry no icon (`no-decorative-icon`): an icon there repeats the words beside it and competes with the marks that matter. A menu row takes one icon, leading its label; a button takes one, leading or trailing it (`prefix` or `suffix`). A mark goes where it says which thing you are looking at; a picture marks a moment. Where each goes:

| Place | Identity mark | Icon | Picture |
| --- | --- | --- | --- |
| A record's row | Yes, 32 px, first | Its kind's icon, as its mark, when it has no mark of its own | No |
| An object's header | Yes, 64 px | No | No |
| A card's or a section's title | Only when the card is one external system | No | No |
| A KPI card | No | Only the trend in the change badge | No |
| A first-use empty state | No | No | Yes, the section's picture, 240 × 176 on a page |
| Nothing yet, no results | No | No | No |
| Finished work, a denial | No | The success mark; the lock mark | No |
| A failed read | No | The warning mark | No |
| A menu row, a button | No | One, leading; a button's may trail | No |
| Entry, a failure page, Downloads, a first run | The Agent's orb where she is the subject | No | Yes, one, whole; Downloads one per app, in its card |

A benefit of nadicode, and each piece of office work Nadia arrives ready for, is drawn on the website by its object: one transparent object of the art direction, something old from Italy with one piece of today in it, in two identity hues, in the family of the Portal's email pictures (the key ring with its security key). The objects are a library (`brand/visual-design/images/website-objects/`, briefed in `brand/refs/briefs/website-benefits.json`), and the same object stands for the same benefit wherever the brand names it: the website, the docs, a deck, a post. On a page they stand at one size, above their title, and never move. Lucide glyphs stay the products' own icons and never stand for a benefit on the website.

## Brand media authority

The company is **nadicode**, lowercase. Its logo is the n, and the name drawn in the same line is the wordmark. Every form is cut from one drawing: a 100-unit x-height, strokes 18 units wide with flat ends, and every bowl and the n's arch a radius of 41. The forms are:

1. **Mark.** The n, the logo itself, as tall as it is wide, with nothing added. It stands for the company wherever the company is recognised by its sign: app headers and compact navigation, the corner of a slide, a sticker, a stamp.
2. **Wordmark.** nadicode in one geometric line with one dot over the i. The dot is the only colour in the logo, where an Agent sits. Use it wherever the company is named: the website's header, documents, proposals, invoices, email signatures, a first slide.
3. **Icon.** The mark on a tile with rounded corners, in one of three fields. The brand tile, the white n on `{colors.identity-cobalto}`, is for favicons, avatars and the website's app icon, and is the one place the brand colour fills a logo's field. The light tile, the `{colors.ink}` n on white, is a desktop app's icon, which keeps it in the system's dark appearance. The dark tile, the white n on `{colors.identity-ink}`, is the icon wherever else it sits on a dark ground: a dark slide, a dark profile, an app's own dark screen.
4. **Signature form.** The light tile with its construction guides in `{colors.logo-guide}`: a dotted circle around the arch whose radius is the golden section of the n's width, the two centre axes, and the n's width and height, each measured 1.000. It is drawn from 128 px up, on light grounds: Nadia's desktop icon, a cover or closing slide, a poster, print; on a dark ground the dark tile stands in for it. Below 128 px a guide is a fraction of a pixel, so the icon drops them; drawn on a screen as a logo, such as at the top of an entry scene, it drops them at any size. Alveare draws its own mark's desktop icon the same way.

The mark and the wordmark never stand side by side: the wordmark already begins with the n, so together they read "n nadicode". Each place takes one of them.

On `{colors.canvas}` and the soft canvases the letters are ink and the dot is `{colors.logo-dot}`; on black and other dark grounds the letters are `{colors.identity-white}` and the dot `{colors.dark-logo-dot}`, a brighter cobalt. `logo-dot` is the only colour a consumer paints the dot with. A one-ink form exists only where a single ink prints: a stamp, an engraving, a one-colour print run. On a photograph, the logo sits in a quiet area or over a scrim that keeps it legible, or on an approved solid field.

Every form is drawn from one geometry source, `@nadicodeai/design-system/assets/logo-geometry`, which holds every dimension, the guides included. Nothing re-authors, traces, typesets or rearranges it: the published files, Nadia's icons, the React adapters and the specimen page (`examples/logo.html`) all render it. React draws `BrandWordmark` or `BrandMark`; there is no lockup.

The clear space is one stroke width of the rendered logo on every side. The minimum sizes are 72 px wide for the wordmark, 12 px for the mark and 16 px for the favicon on screen, and 25 mm and 5 mm in print; the signature form starts at 128 px. A meaningful standalone logo is named `nadicode`; inside a link or control that already has the name, the logo is hidden from assistive technology. Never redraw, retype, stretch, crop, rotate, recolour, outline, shadow, animate or add a gradient to a logo; never add a shape to the mark, move the dot, set the mark beside the wordmark, draw the guides below 128 px, or put ink letters on a dark ground. Other companies' marks, Agent orbs and generated imagery are not nadicode logos.

## Registers

| Register | Its rulebook | Its own tokens | Dark mode |
| --- | --- | --- | --- |
| Website | `apps/website/docs/design-doctrine.md` | `register.website.layout` | A light page; a black section scopes `.dark` for the product views in it |
| Portal | `apps/portal/docs/information-architecture.md` and `apps/portal/docs/design-doctrine.md` | None | System, light or dark, on the `.dark` class |
| Nadia | `apps/nadia/docs/design-doctrine.md` | None | System by default, light or dark, on the `.dark` class |
| Alveare | `apps/alveare/docs/design-doctrine.md` | None | System, light or dark, on Buzz's own theme pair |

- **Website.** The page grammar is "The website column": full-bleed sections on white, soft and black grounds, alternating, with product views on them. Its cards are outlined; its product views take their real heights.
- **Portal.** A product shell on the workspace ground, built from the page compositions and the package components, with `globals.css` giving every elevated surface its tone.
- **Nadia.** Nadia draws her own window through Hermes's `window.shell` area, inside Hermes's app, which owns Tailwind and its own surface layering. Nadia's theme paints Hermes's roles with the shared `semantic` roles and core radii, and imports `@nadicodeai/ui/components.css` with the design system's host stylesheet, so the package components render with the same recipes and glass. The window follows the operating system's material where the system composites it. Hermes answers reduced motion by cutting every transition and animation; Nadia's stylesheet gives her own parts, the package overlays, the entry screen and the waiting line, the reduced-motion policy in "Motion" back.
  Her conversation is one centred column of `56rem` holding her replies, the person's messages and the chat bar: her reply is plain text across the column, only the person's message is a bubble, grey (`{colors.canvas-soft-2}`) at the `raised` height in both modes, at the column's trailing edge, and the chat bar floats over the messages in glass ("Elevation & Depth"). The selected Agent row carries the sidebar's selected fill, as the Agents list draws it.
- **Alveare.** The desktop keeps Buzz's components and layout, styled through an adapter as Nadia styles Hermes: Buzz's colour roles read the shared `semantic` roles, tokens only and never a copied value, every text takes a type role, and Buzz's shared components take the package's geometry, states, focus and motion, over the design system's host stylesheet and `@nadicodeai/ui/components.css`. For the jobs the products share, Alveare mounts the package's own component, or the look the package exports for it, in place of Buzz's: every empty state is `EmptyState`, the account menu is `AccountMenu`, a status is `StatusBadge`, the three dots are `ActionMenu`, Find is `SidebarFind`'s outlined row with its `Kbd`, and a count at a sidebar row's end is `SidebarMenuBadge`. Buzz's colour roles are generated from the `semantic` map of each mode, so the sidebar and the page sit on the canvas in dark as Nadia's do, and the window is solid, as Nadia's is, until a person turns glass on. Agents appear as their orbs, and a person never reads "Buzz". Ruled by the owner.

## Do's and Don'ts

- Do keep the brand still: only Agents, the product at work and the website's shader objects move. Don't animate sections, pictures or scrolling, apart from the bounded homepage figure response and a first use's one arrival in Motion.
- Do use the identity hues at full strength in the places "Colour as stage" lists, with the text pole that clears AA. Don't tint them, grade them, or paint a control, chrome or a status with them.
- Do choose a surface's height by its physical role, and let its recipe show it. Don't add a border, ring, second shadow or grey fill to an elevated surface.
- Do put glass only where content scrolls or moves under it. Don't make content, a menu, a popover, a toast or a dialog glass, or put glass over a plain page.
- Do show authentic captures or real-component scenes as "Product views on the website" specifies, with provenance recorded in the repository. Don't use device frames or invented interfaces.
- Do give each thing the corner its role takes on the scale ("Shapes"). Don't choose a corner at the call site: a raised card, like any medium or large surface, takes the larger corner with its material.
- Do give every line one owner, and draw focus only with the focus outline.
- Do say a status with a `StatusBadge` tone, its shape and a word. Don't use colour alone or a bare dot.
- Do open a failure page with one whole cut-out whose idea says what happened, labelled like a work in a museum, above the words on one centre line, with the recovery action leading and the diagnostic code only for a request that stopped. Don't apologise, don't crop the picture, don't joke about lost work, and don't put an orb on a failure page.
- Do use the dotted field only on a canvas where things are arranged. Don't put it behind lists, dashboards, settings or website sections.
- Do build a page from its composition. Don't assemble a page's header, toolbar, rows or menus by hand.
- Do put navigation in the top bar and the page's actions on its title row. Don't put an action in the top bar or a second sidebar beside the first.
- Do add a shared rule here, and a component's own rule to its usage page, before a product needs it. Don't restate a rule in another document; link to it.

## Token extension

The YAML front matter holds the four groups the DESIGN.md format defines: colors, typography, rounded and spacing. Every other value is authored in `src/tokens/extension.tokens.json`, in the W3C design-tokens format: `core.material` (the scrim and glass values, a `dark-` value beside one that differs in dark), `core.elevation` (the four heights and the glass edge, per mode), `core.motion` (durations and easings), `semantic` (the shared colour roles and radius) and `register.website` (the website column). An alias is written `{core.color.canvas}`. In CSS a token is `--nc-<name>`, an elevation is reached only through `nc-elevation-<height>`, and Tailwind names the values through the `nc-` and `nadicode-` namespaces. The build composes the two into the package's token files and CSS, as the package README describes.
