---
version: 1
name: Canvas
description: >-
  A universal React Native UI kit: one component API and one codebase rendering
  natively on iOS and Android and, through React Native Web, in the browser. Every
  visual variation is a semantic boolean prop; there is no style escape hatch.
colors:
  background: "#f2f2f7"
  surface-sunken: "#fafafd"
  foreground: "#111118"
  card: "#ffffff"
  card-foreground: "#111118"
  popover: "#ffffff"
  popover-foreground: "#111118"
  secondary: "#f1f1f6"
  secondary-foreground: "#111118"
  muted: "#f1f1f6"
  muted-foreground: "#5a5a70"
  subtle-foreground: "#6c6c82"
  accent: "#f1f1f6"
  accent-foreground: "#111118"
  surface-raised: "#e4e4ee"
  destructive: "#d62a4b"
  destructive-foreground: "#ffffff"
  destructive-soft: "rgba(214, 42, 75, 0.1)"
  success: "#0b7a53"
  success-foreground: "#ffffff"
  success-soft: "rgba(11, 122, 83, 0.1)"
  warning: "#a55d00"
  warning-foreground: "#ffffff"
  warning-soft: "rgba(165, 93, 0, 0.1)"
  border: "rgba(17, 17, 24, 0.08)"
  field-border: "rgba(17, 17, 24, 0.16)"
  input: "rgba(17, 17, 24, 0.16)"
  field-fill: "#fafafd"
  hover: "rgba(17, 17, 24, 0.06)"
  highlight: "rgba(255, 255, 255, 0.8)"
  scrim: "rgba(17, 17, 24, 0.3)"
  shade: "rgba(17, 17, 24, 0.05)"
  shade-md: "rgba(17, 17, 24, 0.16)"
  shade-lg: "rgba(17, 17, 24, 0.25)"
  shade-ring: "rgba(17, 17, 24, 0.03)"
  shade-inset: "rgba(255, 255, 255, 0)"
  inverse: "#111118"
  inverse-foreground: "#ffffff"
  destructive-text: "#d62a4b"
  success-text: "#0b7a53"
  warning-text: "#a55d00"
  primary: "#5f3ed8"
  primary-text: "#573bc3"
  primary-foreground: "#ffffff"
  primary-soft: "rgba(95, 62, 216, 0.1)"
  action: "#5f3ed8"
  action-foreground: "#ffffff"
  action-soft: "rgba(95, 62, 216, 0.1)"
  info: "#573bc3"
  info-soft: "rgba(95, 62, 216, 0.1)"
  ring: "rgba(95, 62, 216, 0.28)"
  glow: "rgba(95, 62, 216, 0.4)"
  inverse-primary: "#b5b0ff"
  featured-start: "#5f3ed8"
  featured-middle: "#009bab"
  featured-end: "#00ae7b"
  featured-stack: "rgba(255, 255, 255, 0.5)"
  promo-start: "#5f3ed8"
  promo-middle: "#009bab"
  promo-end: "#00ae7b"
palettes:
  violet: 285
  cyan: 205
  mint: 165
  rose: 20
  amber: 75
colorsDark:
  background: "#0a0a0f"
  surface-sunken: "#0e0e15"
  foreground: "#f4f4f8"
  card: "#12121a"
  card-foreground: "#f4f4f8"
  popover: "#171720"
  popover-foreground: "#f4f4f8"
  secondary: "#1e1e29"
  secondary-foreground: "#f4f4f8"
  muted: "#1e1e29"
  muted-foreground: "#a4a4b6"
  subtle-foreground: "#7f7f93"
  accent: "#1e1e29"
  accent-foreground: "#f4f4f8"
  surface-raised: "#282836"
  destructive: "#ff6b7a"
  destructive-foreground: "#2b0009"
  destructive-soft: "rgba(255, 107, 122, 0.14)"
  success: "#3ddc97"
  success-foreground: "#04291a"
  success-soft: "rgba(61, 220, 151, 0.14)"
  warning: "#f6b84c"
  warning-foreground: "#2a1800"
  warning-soft: "rgba(246, 184, 76, 0.14)"
  border: "rgba(255, 255, 255, 0.08)"
  field-border: "rgba(255, 255, 255, 0.16)"
  input: "rgba(255, 255, 255, 0.16)"
  field-fill: "#0e0e15"
  hover: "rgba(255, 255, 255, 0.05)"
  highlight: "rgba(255, 255, 255, 0.1)"
  scrim: "rgba(5, 5, 10, 0.55)"
  shade: "rgba(0, 0, 0, 0.5)"
  shade-md: "rgba(0, 0, 0, 0.7)"
  shade-lg: "rgba(0, 0, 0, 0.8)"
  shade-ring: "rgba(0, 0, 0, 0)"
  shade-inset: "rgba(255, 255, 255, 0.1)"
  inverse: "#f4f4f8"
  inverse-foreground: "#0a0a0f"
  destructive-text: "#ff6b7a"
  success-text: "#3ddc97"
  warning-text: "#f6b84c"
  primary: "#a298ff"
  primary-text: "#b5b0ff"
  primary-foreground: "#0a0a0f"
  primary-soft: "rgba(162, 152, 255, 0.16)"
  action: "#a298ff"
  action-foreground: "#0a0a0f"
  action-soft: "rgba(162, 152, 255, 0.16)"
  info: "#b5b0ff"
  info-soft: "rgba(162, 152, 255, 0.16)"
  ring: "rgba(162, 152, 255, 0.35)"
  glow: "rgba(162, 152, 255, 0.5)"
  inverse-primary: "#573bc3"
  featured-start: "#a298ff"
  featured-middle: "#36dcec"
  featured-end: "#56ecb5"
  featured-stack: "rgba(255, 255, 255, 0.14)"
  promo-start: "#a298ff"
  promo-middle: "#36dcec"
  promo-end: "#56ecb5"
chart:
  chart-1: "#5f3ed8"
  chart-2: "#009bab"
  chart-3: "#00ae7b"
  chart-4: "#c57800"
  chart-5: "#cf3546"
  chart-6: "#106dce"
  chart-7: "#ad36a7"
  chart-8: "#6a862d"
typography:
  display:
    fontFamily: "\"Instrument Sans\",ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",\"Helvetica Neue\",Arial,sans-serif"
    fontSize: "56px"
    fontWeight: 500
    lineHeight: "56px"
  h1:
    fontFamily: "\"Instrument Sans\",ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",\"Helvetica Neue\",Arial,sans-serif"
    fontSize: "44px"
    fontWeight: 500
    lineHeight: "47px"
  h2:
    fontFamily: "\"Instrument Sans\",ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",\"Helvetica Neue\",Arial,sans-serif"
    fontSize: "34px"
    fontWeight: 500
    lineHeight: "38px"
  h3:
    fontFamily: "\"Instrument Sans\",ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",\"Helvetica Neue\",Arial,sans-serif"
    fontSize: "26px"
    fontWeight: 500
    lineHeight: "30px"
  h4:
    fontFamily: "\"Instrument Sans\",ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",\"Helvetica Neue\",Arial,sans-serif"
    fontSize: "21px"
    fontWeight: 500
    lineHeight: "26px"
  h5:
    fontFamily: "\"Instrument Sans\",ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",\"Helvetica Neue\",Arial,sans-serif"
    fontSize: "17px"
    fontWeight: 600
    lineHeight: "23px"
  lead:
    fontFamily: "\"Instrument Sans\",ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",\"Helvetica Neue\",Arial,sans-serif"
    fontSize: "19px"
    fontWeight: 400
    lineHeight: "29px"
  body:
    fontFamily: "\"Instrument Sans\",ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",\"Helvetica Neue\",Arial,sans-serif"
    fontSize: "15px"
    fontWeight: 400
    lineHeight: "24px"
  label:
    fontFamily: "\"Instrument Sans\",ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",\"Helvetica Neue\",Arial,sans-serif"
    fontSize: "13px"
    fontWeight: 500
    lineHeight: "17px"
  small:
    fontFamily: "\"Instrument Sans\",ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",\"Helvetica Neue\",Arial,sans-serif"
    fontSize: "13px"
    fontWeight: 400
    lineHeight: "20px"
  caption:
    fontFamily: "\"Instrument Sans\",ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",\"Helvetica Neue\",Arial,sans-serif"
    fontSize: "12px"
    fontWeight: 400
    lineHeight: "18px"
  tiny:
    fontFamily: "\"Instrument Sans\",ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",\"Helvetica Neue\",Arial,sans-serif"
    fontSize: "11px"
    fontWeight: 400
    lineHeight: "16px"
  eyebrow:
    fontFamily: "\"Instrument Sans\",ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",\"Helvetica Neue\",Arial,sans-serif"
    fontSize: "11px"
    fontWeight: 500
    lineHeight: "11px"
spacing:
  "0": "0px"
  "1": "4px"
  "2": "8px"
  "3": "12px"
  "4": "16px"
  "5": "20px"
  "6": "24px"
  "7": "28px"
  "8": "32px"
  "9": "36px"
  "10": "40px"
  "11": "44px"
  "12": "48px"
  "14": "56px"
  "16": "64px"
  "20": "80px"
  "24": "96px"
  "28": "112px"
  "32": "128px"
  "36": "144px"
  "40": "160px"
  "48": "192px"
  "56": "224px"
  "64": "256px"
  px: "1px"
  "0.5": "2px"
  "1.5": "6px"
  "2.5": "10px"
  "3.5": "14px"
rounded:
  none: "0px"
  sm: "2px"
  base: "4px"
  md: "6px"
  lg: "8px"
  xl: "12px"
  "2xl": "16px"
  "3xl": "24px"
  full: "9999px"
shadows:
  none: none
  sm: "inset 0px 1px 0px 0px rgba(255, 255, 255, 0),0px 1px 2px 0px rgba(17, 17, 24, 0.05),0px 0px 0px 1px rgba(17, 17, 24, 0.03)"
  md: "inset 0px 1px 0px 0px rgba(255, 255, 255, 0),0px 12px 32px -10px rgba(17, 17, 24, 0.16),0px 0px 0px 1px rgba(17, 17, 24, 0.03)"
  lg: "inset 0px 1px 0px 0px rgba(255, 255, 255, 0),0px 30px 80px -16px rgba(17, 17, 24, 0.25),0px 0px 0px 1px rgba(17, 17, 24, 0.03)"
  base: "var(--shadow-sm)"
  xl: "var(--shadow-lg)"
motion:
  duration-fast: "120ms"
  duration-base: "180ms"
  duration-slow: "280ms"
  ease-standard: "cubic-bezier(0.2,0,0,1)"
  ease-out: "cubic-bezier(0,0,0.2,1)"
  ease-in-out: "cubic-bezier(0.4,0,0.2,1)"
breakpoints:
  sm: "640px"
  md: "768px"
  lg: "1024px"
  xl: "1280px"
  "2xl": "1536px"
widths:
  xxxs: "192px"
  xxs: "256px"
  xs: "320px"
  sm: "384px"
  md: "448px"
  lg: "512px"
  xl: "576px"
  xxl: "672px"
  xxxl: "768px"
  wide: "896px"
  wider: "1024px"
  widest: "1152px"
  page: "1280px"
touchTargets:
  ios: "44px"
  android: "48px"
  web: none
fontWeights:
  thin: "100"
  extralight: "200"
  light: "300"
  normal: "400"
  medium: "500"
  semibold: "600"
  bold: "700"
  extrabold: "800"
  black: "900"
components:
  stats:
    stat-radius: "14px"
    stat-pad: "16px"
    stat-gap: "12px"
    stat-shadow: none
    stat-value-lh: "28px"
    stat-value-tracking: "-0.56px"
    stat-label-tracking: "0px"
  emptystate:
    empty-radius: "18px"
  card:
    card-radius: "16px"
    card-shadow: "var(--shadow-sm)"
    card-shadow-raised: "var(--shadow-lg)"
  autocomplete:
    ac-radius: "10px"
    ac-radius-bottom: "10px"
    ac-menu-radius: "16px"
    ac-row-radius: "8px"
  avatar:
    avatar-square-radius: "11px"
  buttongroup:
    seg-radius: "7px"
    seg-track-radius: "10px"
    seg-inner-radius: "7px"
  button:
    btn-radius: "10px"
  checkbox:
    check-radius: "5px"
  chip:
    chip-radius: "9999px"
  dropdown:
    menu-shadow: "var(--shadow-md)"
    menu-radius: "16px"
    menu-row-radius: "8px"
  emblem:
    emblem-radius-small: "8px"
    emblem-radius-default: "11px"
    emblem-radius-large: "16px"
  input:
    field-radius: "10px"
    field-radius-bottom: "10px"
  pagination:
    page-radius: "8px"
  select:
    select-panel-shadow: "var(--shadow-md)"
    select-radius: "10px"
    select-radius-bottom: "10px"
    select-panel-radius: "16px"
    select-row-radius: "8px"
  stepper:
    stepper-btn-radius: "0px"
    stepper-group-radius: "10px"
  slider:
    slider-track-radius: "9999px"
    slider-inner-radius: "9999px"
    slider-thumb-radius: "9999px"
  radio:
    radio-section-radius: "0px"
  textarea:
    field-radius: "10px"
    field-radius-bottom: "10px"
  accordion:
    acc-card-radius: "16px"
    acc-container-radius: "0px"
  alertdialog:
    ad-radius: "24px"
    ad-btn-radius: "10px"
  alert:
    alert-radius: "14px"
  descriptionlist:
    dl-shadow: "var(--shadow-sm)"
    dl-radius: "16px"
  feed:
    feed-radius: "16px"
  gridlist:
    grid-gallery-radius: "14px"
  mediaobject:
    media-radius: "12px"
    media-shadow: none
    media-icon-radius: "11px"
  stackedlist:
    list-shadow: "var(--shadow-sm)"
    list-radius: "14px"
  actionsheet:
    sheet-card-radius-top: "16px"
    sheet-card-radius: "16px"
    sheet-row-radius: "0px"
    sheet-cancel-radius: "16px"
  board:
    board-col-radius: "14px"
    board-card-radius: "10px"
  calendar:
    cal-radius: "16px"
    cal-chevron-radius: "10px"
  carousel:
    carousel-slide-radius: "16px"
  dialog:
    dialog-shadow: "var(--shadow-lg)"
    dialog-radius: "20px"
    alert-btn-radius: "10px"
  drawer:
    drawer-side-radius: "22px"
    drawer-sheet-radius: "22px"
  navbar:
    nav-link-radius: "9999px"
  sidebar:
    side-toggle-radius: "10px"
    side-row-radius: "9px"
  steps:
    steps-connector-radius: "0px"
  toast:
    toast-radius: "16px"
    toast-radius-multiline: "16px"
  badge:
    badge-radius: "9999px"
  inputotp:
    otp-radius: "10px"
    otp-inner-radius: "10px"
  popover:
    popover-radius: "16px"
    popover-shadow: "var(--shadow-lg)"
  swatch:
    swatch-radius-small: "12px"
    swatch-radius-default: "12px"
    swatch-radius-large: "12px"
  tooltip:
    tip-radius: "8px"
    tip-shadow: "var(--shadow-md)"
  command:
    cmd-radius: "18px"
  datatable:
    table-radius: "14px"
  draghandle:
    drag-handle-size: "32px"
    drag-handle-radius: "10px"
    drag-handle-icon: "15px"
  tabbar:
    tabbar-radius: "9999px"
    tabbar-item-radius: "9999px"
  tabs:
    tab-track-radius: "0px"
    tab-item-radius: "0px"
    tab-pill-track-radius: "12px"
    tab-pill-radius: "9px"
    tab-v-radius: "9px"
platformSkins:
  ios:
    stats:
      stat-value-lh: "34px"
    emptystate:
      empty-radius: "18px"
    card:
      card-radius: "12px"
      card-shadow: "var(--shadow-sm)"
      card-shadow-raised: "var(--shadow-lg)"
    autocomplete:
      ac-radius: "8px"
      ac-radius-bottom: "8px"
      ac-menu-radius: "12px"
      ac-row-radius: "8px"
    buttongroup:
      seg-radius: "9999px"
      seg-track-radius: "9999px"
      seg-inner-radius: "9999px"
    button:
      btn-radius: "9999px"
    checkbox:
      check-radius: "9999px"
    dropdown:
      menu-shadow: "var(--shadow-lg)"
      menu-radius: "26px"
      menu-row-radius: "0px"
    input:
      field-radius: "8px"
      field-radius-bottom: "8px"
    select:
      select-panel-shadow: "var(--shadow-lg)"
      select-radius: "8px"
      select-radius-bottom: "8px"
      select-panel-radius: "26px"
      select-row-radius: "0px"
    stepper:
      stepper-group-radius: "9999px"
    radio:
      radio-section-radius: "26px"
    textarea:
      field-radius: "8px"
      field-radius-bottom: "8px"
    accordion:
      acc-card-radius: "12px"
      acc-container-radius: "12px"
    alertdialog:
      ad-radius: "14px"
      ad-btn-radius: "0px"
    descriptionlist:
      dl-shadow: none
      dl-radius: "26px"
    gridlist:
      grid-gallery-radius: "12px"
    stackedlist:
      list-shadow: none
      list-radius: "26px"
    actionsheet:
      sheet-card-radius-top: "34px"
      sheet-card-radius: "34px"
      sheet-row-radius: "9999px"
      sheet-cancel-radius: "9999px"
    board:
      board-col-radius: "14px"
      board-card-radius: "10px"
    calendar:
      cal-radius: "12px"
      cal-chevron-radius: "9999px"
    dialog:
      dialog-shadow: "var(--shadow-lg)"
      dialog-radius: "28px"
      alert-btn-radius: "9999px"
    drawer:
      drawer-side-radius: "38px"
      drawer-sheet-radius: "38px"
    navbar:
      nav-link-radius: "9999px"
    sidebar:
      side-toggle-radius: "0px"
      side-row-radius: "9999px"
    inputotp:
      otp-radius: "8px"
      otp-inner-radius: "8px"
    popover:
      popover-radius: "26px"
    command:
      cmd-radius: "18px"
    datatable:
      table-radius: "12px"
    draghandle:
      drag-handle-size: "32px"
      drag-handle-radius: "10px"
      drag-handle-icon: "15px"
    tabbar:
      tabbar-radius: "9999px"
      tabbar-item-radius: "9999px"
    tabs:
      tab-track-radius: "9999px"
      tab-item-radius: "9999px"
      tab-pill-track-radius: "9999px"
      tab-pill-radius: "9999px"
      tab-v-radius: "10px"
  android:
    stats:
      stat-value-lh: "34px"
    emptystate:
      empty-radius: "18px"
    card:
      card-radius: "12px"
      card-shadow: "var(--shadow-sm)"
      card-shadow-raised: "0px 4px 6px rgba(0,0,0,0.1)"
    autocomplete:
      ac-radius: "4px"
      ac-radius-bottom: "0px"
      ac-menu-radius: "4px"
      ac-row-radius: "0px"
    buttongroup:
      seg-radius: "0px"
      seg-track-radius: "9999px"
      seg-inner-radius: "0px"
    button:
      btn-radius: "9999px"
    checkbox:
      check-radius: "2px"
    chip:
      chip-radius: "8px"
    dropdown:
      menu-shadow: "var(--shadow-md)"
      menu-radius: "4px"
      menu-row-radius: "0px"
    input:
      field-radius: "4px"
      field-radius-bottom: "0px"
    select:
      select-panel-shadow: "var(--shadow-md)"
      select-radius: "4px"
      select-radius-bottom: "0px"
      select-panel-radius: "4px"
      select-row-radius: "0px"
    stepper:
      stepper-btn-radius: "9999px"
      stepper-group-radius: "0px"
    slider:
      slider-track-radius: "8px"
      slider-inner-radius: "2px"
      slider-thumb-radius: "9999px"
    textarea:
      field-radius: "4px"
      field-radius-bottom: "0px"
    alertdialog:
      ad-radius: "28px"
      ad-btn-radius: "9999px"
    stackedlist:
      list-shadow: none
      list-radius: "12px"
    actionsheet:
      sheet-card-radius-top: "28px"
      sheet-card-radius: "0px"
      sheet-cancel-radius: "0px"
    board:
      board-col-radius: "14px"
      board-card-radius: "10px"
    calendar:
      cal-radius: "12px"
      cal-chevron-radius: "9999px"
    carousel:
      carousel-slide-radius: "28px"
    dialog:
      dialog-shadow: "var(--shadow-lg)"
      dialog-radius: "28px"
      alert-btn-radius: "9999px"
    drawer:
      drawer-side-radius: "16px"
      drawer-sheet-radius: "28px"
    navbar:
      nav-link-radius: "9999px"
    sidebar:
      side-toggle-radius: "9999px"
      side-row-radius: "9999px"
    toast:
      toast-radius: "4px"
      toast-radius-multiline: "4px"
    inputotp:
      otp-radius: "4px"
      otp-inner-radius: "4px"
    tooltip:
      tip-radius: "4px"
      tip-shadow: none
    command:
      cmd-radius: "18px"
    draghandle:
      drag-handle-size: "40px"
      drag-handle-radius: "9999px"
      drag-handle-icon: "15px"
    tabbar:
      tabbar-radius: "0px"
      tabbar-item-radius: "0px"
    tabs:
      tab-track-radius: "0px"
      tab-item-radius: "0px"
      tab-pill-track-radius: "9999px"
      tab-pill-radius: "9999px"
      tab-v-radius: "9999px"
---

<!-- The frontmatter above and the token tables below are GENERATED from
     src/style/tokens.ts and styles/tokens/*.css by tools/designmd. Edit those, or
     tools/designmd/template.md for the prose, then run `bun run designmd:gen`. -->

# Canvas

One component API, three native looks. Canvas is a React Native UI kit that renders
natively on iOS and Android and, through React Native Web, in a browser, from a single
codebase. Each component ships a shared shell and a skin per platform, so a Button is
an iOS capsule with a semibold label, a Material 3 stadium with a ripple, and a
10px-rounded web control in the v4 "Light as material" colors (one accent hue for
selection and calls to action, near-black or near-white surfaces, glass that frosts and
saturates what sits behind it), without the caller choosing between them.

The rest of this document is what an agent or a developer needs to build with the kit
and get it right: the values, and the four rules that are easy to break by accident.

## Components

**Every visual variation is a boolean prop, named for what it means.** Passing the prop
turns it on; the prop name IS the value.

```jsx
<Button primary large>Save</Button>
<Button destructive>Delete</Button>
<Button ghost small>Cancel</Button>
<Card raised>…</Card>
```

String-valued enum props are not part of this API and never will be. `variant="primary"`,
`size="lg"`, `tone="destructive"` and `elevation="raised"` do not exist; passing one does
nothing, silently, because React drops unknown props.

Props are grouped into axes. Different axes combine freely; within one axis you pass at
most one:

- **Intent**: `primary`, `secondary`, `destructive`, `ghost`, `outline`, `link`. Pass
  none for the default look.
- **Size**: `small`, `large`. Pass none for the default.
- **Density**: `compact`, `comfortable`. Pass none for the default.
- **State and layout**, orthogonal booleans that stack: `loading`, `disabled`, `block`,
  `rounded`, and the like.

So `<Button primary large loading block>` is four props from four axes, all applied. If
two props from one axis arrive, the component resolves to one by a documented
precedence and never stacks them.

**A component owns the text it labels.** The title is `children`, the muted second line
is the `description` prop. The component owns the label typography, the stacked column,
the alignment of its indicator to the first line, and the whole-row tap target.

```jsx
<Checkbox defaultChecked description="Get notified when activity happens.">
  Email notifications
</Checkbox>
```

Composing that out of a Row, a Checkbox and two Typography nodes splits the tap target
so only the box toggles, drifts from the control's type scale, and is a bug wherever it
appears.

## Colors

Semantic tokens, one set per accent palette and scheme, from the Canvas v4 design
hand-off (vendored at `tools/v4/handoff`): its neutrals, status colours and glass as
literal values, and its accent and chart series as `oklch(L C hue)` formulas converted to
sRGB within one channel unit of what a browser paints. Five palettes, each one hue for the
whole accent: violet (the default), cyan, mint, rose and amber, and a palette applies in
both schemes. Read any pair with `colorsFor(palette, scheme)`. D14 preserves the
hand-off's colors literally, including its 16% control boundary, translucent focus ring
and unadjusted text inks. Contrast measurements are recorded separately; they do not
recolor the reference. Components read
the tokens through `useTheme()`; the scheme follows the OS unless `<ThemeProvider dark>`
or `<ThemeProvider light>` forces one, and `<ThemeProvider cyan>` (or `mint`, `rose`,
`amber`) picks the accent. On the web the same values ship as custom properties in
`styles/canvas.css`, generated from the token table: dark keys off a `.dark` class on the
root rather than `prefers-color-scheme`, and a palette is one `--hue` set by
`data-palette` (`setPalette`) on the root or on any wrapper.

Two roles carry the brand. `primary` is the selection color: checked, selected and
current states, links and the focus ring. `action` is the call-to-action fill: primary
buttons, meters, count badges. In every palette both are the accent; a theme that
overrides `primary` alone repaints both, since `action` follows an overriding `primary`
unless the theme sets it too. `scrim` dims the page behind every modal (under glass on the web it also
blurs it by 2 px). `inverse`,
`inverse-foreground` and `inverse-primary` are the toast pill, its ink and the action on
it (every toast paints them: the web and iOS pill and the Android snackbar;
`inverse-primary` is the palette's own brand hue lightened to read on the pill). The `-soft` washes, `shade` (the shadow tint) and
`field-fill` are optional roles the skins adopt as they take the v4 look, beside the
new `surface-sunken`, `surface-raised`, `subtle-foreground`, `glow` and the shade roles; a
theme that omits any of these keeps the older fallbacks.

<!-- @generated:colors -->
| Token | Light (violet) | Dark (violet) |
| --- | --- | --- |
| `background` | `#f2f2f7` | `#0a0a0f` |
| `surface-sunken` | `#fafafd` | `#0e0e15` |
| `foreground` | `#111118` | `#f4f4f8` |
| `card` | `#ffffff` | `#12121a` |
| `card-foreground` | `#111118` | `#f4f4f8` |
| `popover` | `#ffffff` | `#171720` |
| `popover-foreground` | `#111118` | `#f4f4f8` |
| `secondary` | `#f1f1f6` | `#1e1e29` |
| `secondary-foreground` | `#111118` | `#f4f4f8` |
| `muted` | `#f1f1f6` | `#1e1e29` |
| `muted-foreground` | `#5a5a70` | `#a4a4b6` |
| `subtle-foreground` | `#6c6c82` | `#7f7f93` |
| `accent` | `#f1f1f6` | `#1e1e29` |
| `accent-foreground` | `#111118` | `#f4f4f8` |
| `surface-raised` | `#e4e4ee` | `#282836` |
| `destructive` | `#d62a4b` | `#ff6b7a` |
| `destructive-foreground` | `#ffffff` | `#2b0009` |
| `destructive-soft` | `rgba(214, 42, 75, 0.1)` | `rgba(255, 107, 122, 0.14)` |
| `success` | `#0b7a53` | `#3ddc97` |
| `success-foreground` | `#ffffff` | `#04291a` |
| `success-soft` | `rgba(11, 122, 83, 0.1)` | `rgba(61, 220, 151, 0.14)` |
| `warning` | `#a55d00` | `#f6b84c` |
| `warning-foreground` | `#ffffff` | `#2a1800` |
| `warning-soft` | `rgba(165, 93, 0, 0.1)` | `rgba(246, 184, 76, 0.14)` |
| `border` | `rgba(17, 17, 24, 0.08)` | `rgba(255, 255, 255, 0.08)` |
| `field-border` | `rgba(17, 17, 24, 0.16)` | `rgba(255, 255, 255, 0.16)` |
| `input` | `rgba(17, 17, 24, 0.16)` | `rgba(255, 255, 255, 0.16)` |
| `field-fill` | `#fafafd` | `#0e0e15` |
| `hover` | `rgba(17, 17, 24, 0.06)` | `rgba(255, 255, 255, 0.05)` |
| `highlight` | `rgba(255, 255, 255, 0.8)` | `rgba(255, 255, 255, 0.1)` |
| `scrim` | `rgba(17, 17, 24, 0.3)` | `rgba(5, 5, 10, 0.55)` |
| `shade` | `rgba(17, 17, 24, 0.05)` | `rgba(0, 0, 0, 0.5)` |
| `shade-md` | `rgba(17, 17, 24, 0.16)` | `rgba(0, 0, 0, 0.7)` |
| `shade-lg` | `rgba(17, 17, 24, 0.25)` | `rgba(0, 0, 0, 0.8)` |
| `shade-ring` | `rgba(17, 17, 24, 0.03)` | `rgba(0, 0, 0, 0)` |
| `shade-inset` | `rgba(255, 255, 255, 0)` | `rgba(255, 255, 255, 0.1)` |
| `inverse` | `#111118` | `#f4f4f8` |
| `inverse-foreground` | `#ffffff` | `#0a0a0f` |
| `destructive-text` | `#d62a4b` | `#ff6b7a` |
| `success-text` | `#0b7a53` | `#3ddc97` |
| `warning-text` | `#a55d00` | `#f6b84c` |
| `primary` | `#5f3ed8` | `#a298ff` |
| `primary-text` | `#573bc3` | `#b5b0ff` |
| `primary-foreground` | `#ffffff` | `#0a0a0f` |
| `primary-soft` | `rgba(95, 62, 216, 0.1)` | `rgba(162, 152, 255, 0.16)` |
| `action` | `#5f3ed8` | `#a298ff` |
| `action-foreground` | `#ffffff` | `#0a0a0f` |
| `action-soft` | `rgba(95, 62, 216, 0.1)` | `rgba(162, 152, 255, 0.16)` |
| `info` | `#573bc3` | `#b5b0ff` |
| `info-soft` | `rgba(95, 62, 216, 0.1)` | `rgba(162, 152, 255, 0.16)` |
| `ring` | `rgba(95, 62, 216, 0.28)` | `rgba(162, 152, 255, 0.35)` |
| `glow` | `rgba(95, 62, 216, 0.4)` | `rgba(162, 152, 255, 0.5)` |
| `inverse-primary` | `#b5b0ff` | `#573bc3` |
| `featured-start` | `#5f3ed8` | `#a298ff` |
| `featured-middle` | `#009bab` | `#36dcec` |
| `featured-end` | `#00ae7b` | `#56ecb5` |
| `featured-stack` | `rgba(255, 255, 255, 0.5)` | `rgba(255, 255, 255, 0.14)` |
| `promo-start` | `#5f3ed8` | `#a298ff` |
| `promo-middle` | `#009bab` | `#36dcec` |
| `promo-end` | `#00ae7b` | `#56ecb5` |
<!-- @/generated -->

The chart series is a fixed assignment: series one is always `chart-1`, and filtering a
series out never renumbers the rest.

**Glass is a theming mode, not a component prop.** `<ThemeProvider glass>` forces the
material on, `<ThemeProvider solid>` forces the flat look, and passing neither resolves
to the platform default: glass on iOS 26 and above, where Apple makes Liquid Glass the
system material, and solid everywhere else. Under glass every surface renders through
the material, layered, each layer with its own under-fill: the functional shells and
overlays (navbars, the tab bar, the sidebar, dialogs, sheets, drawers, popovers, the
command palette) take the sheer `glass-tint`; the content panes (cards, lists, tables,
calendars, charts, alerts) the denser `glass-tint-content`, so their text keeps its
contrast; the controls (fields, buttons, tabs, chips, badges) the bright
`glass-tint-control` puck, brand-tinted where the fill is the brand; and the surfaces
a user reads and acts on (menus, selects, alert dialogs, toasts, tooltips) the
densest `glass-tint-dense`. Radios, checkboxes and switches are marks inside content
and keep their solid look. Fills inside a surface become ink tints, a brand puck
stays as sheer as its ink's 4.5:1 allows, and solid mode is untouched. There is
no per-component `glass` prop, and hand-painting the material onto a component is not
a supported way to get one.

## Typography

Three faces and one scale, the hand-off's. Instrument Sans sets the interface,
Bricolage Grotesque the web skins' headings and big numbers (the `display` role of the
ThemeProvider's `fonts`), and JetBrains Mono code and meta; iOS and Android set their text
in SF Pro and Roboto. Titles are medium weight with tightened tracking, stepping from the
display 56 through 44, 34, 26 and 21 to the 17 h5; body copy is 15 regular, small 13,
caption 12 and tiny 11, and the eyebrow an 11px mono uppercase label tracked +0.1em. The
semantic roles below are what the Typography component renders. Headings lead tighter
than body copy. Dynamic Type, Android font scale and browser zoom all still apply.

<!-- @generated:typography -->
| Role | Size | Line height | Weight |
| --- | --- | --- | --- |
| `display` | 56px | 56px | 500 |
| `h1` | 44px | 47px | 500 |
| `h2` | 34px | 38px | 500 |
| `h3` | 26px | 30px | 500 |
| `h4` | 21px | 26px | 500 |
| `h5` | 17px | 23px | 600 |
| `lead` | 19px | 29px | 400 |
| `body` | 15px | 24px | 400 |
| `label` | 13px | 17px | 500 |
| `small` | 13px | 20px | 400 |
| `caption` | 12px | 18px | 400 |
| `tiny` | 11px | 16px | 400 |
| `eyebrow` | 11px | 11px | 500 |
<!-- @/generated -->

## Layout

The spacing scale, in pixels:

<!-- @generated:spacing -->
`0` 0 · `1` 4 · `2` 8 · `3` 12 · `4` 16 · `5` 20 · `6` 24 · `7` 28 · `8` 32 · `9` 36 · `10` 40 · `11` 44 · `12` 48 · `14` 56 · `16` 64 · `20` 80 · `24` 96 · `28` 112 · `32` 128 · `36` 144 · `40` 160 · `48` 192 · `56` 224 · `64` 256 · `px` 1 · `0.5` 2 · `1.5` 6 · `2.5` 10 · `3.5` 14
<!-- @/generated -->

Layout is composed from the kit's own primitives rather than from raw flex styles. A
`Row` or `Column` takes a gap by name (`tight`, `snug`, `cozy`, `relaxed`, `loose`) and
alignment as booleans. Equal-width tiles that renumber their columns are a `Grid`, with
a `minTileWidth` floor and a `columns` cap. Content-sized rows that stack when narrow
are a `Row stacks`.

## Shapes

Corner radius by role and the platform touch minimums, which differ by design. Every
corner is a role of its platform's row, named where the corner is written: the roles
every platform draws are `shape` in `src/style/tokens.ts`, below, and the roles only one
platform or only the kit draws are that platform's own, in the second table. The web
follows the v4 hand-off: 10 on rectangular controls and fields, 16 on cards and menus, 20
on dialogs, 22 on sheets and the app shell, 12 on tiles, 5 on the checkbox box, and a
pill on every capsule.
iOS draws capsule controls, 8 on fields, 12 on cards, 26 on menus, 28 on dialogs, the 38
sheet and the circular selection checkbox, with Apple's continuous curve; Material 3
draws stadium controls, 4 on the filled field's top corners and on menus, its 12 medium
shape on cards and 28 on dialogs and sheets.

<!-- @generated:shapes -->
| Platform | Control | Field | Card | Dialog | Menu | Sheet | Checkbox | Pill | Tile | Minimum touch target |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| web | 10px | 10px | 16px | 20px | 16px | 22px | 5px | 9999px | 12px | 0px |
| ios | 9999px | 8px | 12px | 28px | 26px | 38px | 9999px | 9999px | 12px | 44px |
| android | 9999px | 4px | 12px | 28px | 4px | 28px | 2px | 9999px | 12px | 48px |
<!-- @/generated -->

Each platform's own roles: on the web, the key step on keycaps and inline
boxes, its chip step on the tooltip, a calendar event and a chart's bar, and the smaller
chart marks; on iOS, the action sheet, the inset grouped list and the calendar event; on
Android, each Material 3 component at the step of the M3 scale its spec names, and the
kit's own Android parts at a step of the same scale.

<!-- @generated:platform-shapes -->
| Platform | Role | Corner |
| --- | --- | --- |
| web | `chartTrack` | 6px |
| web | `chartSelectedBar` | 6px |
| web | `chartFlagDense` | 9px |
| web | `chartFlagCompact` | 7px |
| web | `mapFrame` | 14px |
| web | `mapZoom` | 10px |
| web | `activityCell` | 3px |
| web | `serviceFrame` | 14px |
| web | `stackEnd` | 7px |
| web | `stackInner` | 3px |
| web | `stackEndCompact` | 4px |
| web | `stackInnerCompact` | 2px |
| web | `treemapSmall` | 6px |
| web | `treemapTiny` | 5px |
| web | `treemapSmallest` | 4px |
| web | `candleShowcase` | 1px |
| web | `boardMetadata` | 6px |
| web | `insetRow` | 10px |
| web | `disclosureCard` | 14px |
| web | `codeCard` | 14px |
| web | `mediaFrame` | 14px |
| web | `brandMark` | 9px |
| web | `avatarSquareTiny` | 6px |
| web | `avatarSquareSmall` | 8px |
| web | `avatarSquare` | 11px |
| web | `avatarSquareLarge` | 16px |
| web | `emblemSmall` | 8px |
| web | `emblem` | 11px |
| web | `emblemLarge` | 16px |
| web | `emblemSoft` | 12px |
| web | `dismiss` | 6px |
| web | `announcement` | 12px |
| web | `referenceCard` | 14px |
| web | `referenceSource` | 6px |
| web | `pageCell` | 8px |
| web | `menuRow` | 8px |
| web | `commandPanel` | 18px |
| web | `commandRow` | 9px |
| web | `callout` | 14px |
| web | `alertDialog` | 24px |
| web | `emptyState` | 18px |
| web | `key` | 6px |
| web | `keySmall` | 5px |
| web | `keyTiny` | 4px |
| web | `inlineCode` | 5px |
| web | `optionChip` | 7px |
| web | `optionTile` | 8px |
| web | `tooltip` | 8px |
| web | `event` | 6px |
| web | `bar` | 3px |
| web | `mark` | 4px |
| web | `cell` | 2px |
| web | `candle` | 1.5px |
| web | `chartTile` | 8px |
| web | `heatCell` | 2.5px |
| web | `sparkTrack` | 6px |
| web | `funnelBar` | 6px |
| web | `listBar` | 8px |
| web | `stripBar` | 1px |
| web | `sparkBar` | 2px |
| web | `chartFlag` | 8px |
| web | `zoomControl` | 8px |
| web | `tableFrame` | 14px |
| web | `listFrame` | 14px |
| web | `gridTile` | 14px |
| web | `statTile` | 14px |
| web | `statSwatch` | 2px |
| web | `dragGhost` | 12px |
| web | `composer` | 16px |
| web | `bubble` | 16px |
| web | `bubbleTail` | 4px |
| web | `assistantComposer` | 12px |
| web | `assistantBubble` | 10px |
| web | `navRow` | 9px |
| web | `navRowCompact` | 8px |
| web | `rail` | 12px |
| web | `floatingRail` | 14px |
| web | `navBar` | 14px |
| web | `lane` | 14px |
| web | `laneCard` | 10px |
| web | `panel` | 18px |
| web | `stage` | 20px |
| web | `copyButton` | 8px |
| web | `cardSnippet` | 10px |
| web | `showcaseCard` | 20px |
| web | `heroStage` | 24px |
| web | `swatchBar` | 7px |
| web | `swatchStep` | 3px |
| web | `rampBar` | 10px |
| web | `iconCell` | 10px |
| web | `stepMark` | 10px |
| web | `terminalScreen` | 12px |
| ios | `actionSheet` | 34px |
| ios | `groupedList` | 26px |
| ios | `event` | 6px |
| ios | `searchField` | 10px |
| ios | `navRow` | 10px |
| ios | `alertDialog` | 14px |
| android | `chip` | 8px |
| android | `event` | 8px |
| android | `sliderTrack` | 8px |
| android | `tooltip` | 4px |
| android | `snackbar` | 4px |
| android | `navigationDrawer` | 16px |
| android | `carouselItem` | 28px |
| android | `tabIndicator` | 3px |
| android | `sliderTrackInner` | 2px |
<!-- @/generated -->

Beyond a role, a corner is the pill (9999, which rounds every capsule and circle to half
its shorter side), square, or concentric with what contains it: the container's corner
less the inset between them. Nothing else: not a number that happens to equal a role, not
half of a height, not a step of the `radius` ladder (that ladder is the app's, which Image
and Video take through their `radius` prop). A skin reads only its own platform's row,
and only the roles its component draws, listed below. A native skin draws a part the
web's way only by sharing the web skin's part (the same part of the web skin draws it from
the same place), and only where its platform gives that part no shape: the component's
row in PLATFORM-REFERENCES.md says the platform ships no control for the job, or the part
is declared with the reason the platform's control does not shape it. A constant, helper
or component no platform names, in any module, is drawn by every skin that uses it; a
shell's own code is drawn by every platform when every platform's entry builds with it,
and a function only some entries build with (a helper that adjusts one platform's skin) is
drawn by those entries' platforms.

<!-- @generated:shape-roles -->
| Role | Drawn by |
| --- | --- |
| `control` | `atoms/button`, `atoms/button-group`, `atoms/skeleton`, `atoms/stepper`, `atoms/tooltip`, `molecules/card`, `molecules/code-block`, `molecules/stacked-lists`, `organisms/calendar`, `organisms/data-table`, `organisms/dialog`, `organisms/drag-drop`, `organisms/navbars`, `organisms/row-menu`, `organisms/sidebar`, `organisms/toast` |
| `field` | `atoms/autocomplete`, `atoms/input`, `atoms/input-otp`, `atoms/select`, `atoms/textarea`, `organisms/command`, `organisms/data-table` |
| `card` | `atoms/autocomplete`, `atoms/skeleton`, `charts/shared`, `molecules/accordion`, `molecules/action-panels`, `molecules/card`, `molecules/code-block`, `molecules/collapsible`, `molecules/description-lists`, `molecules/empty-state`, `molecules/feeds`, `molecules/stacked-lists`, `molecules/stats`, `organisms/calendar`, `organisms/carousel`, `organisms/chat`, `organisms/data-table`, `organisms/flow-editor`, `organisms/navbars`, `organisms/row-menu`, `organisms/sidebar`, `organisms/toast` |
| `dialog` | `molecules/alert-dialog`, `organisms/dialog` |
| `menu` | `atoms/autocomplete`, `atoms/button-group`, `atoms/dropdown`, `atoms/popover`, `atoms/select`, `organisms/action-sheet`, `organisms/calendar`, `organisms/row-menu`, `style` |
| `sheet` | `atoms/popover`, `organisms/action-sheet`, `organisms/drawer` |
| `checkbox` | `atoms/checkbox` |
| `pill` | `atoms/badge`, `atoms/button`, `atoms/button-group`, `atoms/chip`, `atoms/input`, `organisms/navbars`, `organisms/tabs` |
| `tile` | `atoms/listbox`, `atoms/radio`, `atoms/swatch`, `molecules/card`, `molecules/grid-lists`, `molecules/media-objects`, `organisms/dashboard-grid`, `organisms/flow-editor`, `organisms/tabs` |
| `chartTrack` | `charts/bullet-chart` |
| `chartSelectedBar` | `charts/chart` |
| `chartFlagDense` | `charts/candlestick-chart`, `charts/geo-map` |
| `chartFlagCompact` | `charts/chart` |
| `mapFrame` | `charts/geo-map` |
| `mapZoom` | `charts/geo-map` |
| `activityCell` | `charts/heatmap`, `charts/uptime-bar` |
| `serviceFrame` | `charts/service-health-list` |
| `stackEnd` | `charts/stacked-bar` |
| `stackInner` | `charts/stacked-bar` |
| `stackEndCompact` | `charts/stacked-bar` |
| `stackInnerCompact` | `charts/stacked-bar` |
| `treemapSmall` | `charts/treemap` |
| `treemapTiny` | `charts/treemap` |
| `treemapSmallest` | `charts/treemap` |
| `candleShowcase` | `charts/candlestick-chart` |
| `boardMetadata` | `molecules/card` |
| `insetRow` | `molecules/card`, `molecules/collapsible` |
| `disclosureCard` | `molecules/collapsible` |
| `codeCard` | `molecules/code-block` |
| `mediaFrame` | `atoms/qrcode`, `atoms/video` |
| `brandMark` | `atoms/avatar` |
| `avatarSquareTiny` | `atoms/avatar` |
| `avatarSquareSmall` | `atoms/avatar` |
| `avatarSquare` | `atoms/avatar` |
| `avatarSquareLarge` | `atoms/avatar` |
| `emblemSmall` | `atoms/emblem`, `molecules/media-objects` |
| `emblem` | `atoms/emblem`, `molecules/media-objects` |
| `emblemLarge` | `atoms/emblem` |
| `emblemSoft` | `atoms/emblem` |
| `dismiss` | `molecules/alert`, `organisms/toast` |
| `announcement` | `molecules/alert` |
| `referenceCard` | `molecules/card` |
| `referenceSource` | `molecules/card` |
| `pageCell` | `atoms/pagination` |
| `menuRow` | `atoms/autocomplete`, `atoms/select`, `organisms/row-menu`, `style` |
| `commandPanel` | `organisms/command` |
| `commandRow` | `organisms/command` |
| `callout` | `molecules/alert`, `molecules/card` |
| `alertDialog` | `molecules/alert-dialog` |
| `emptyState` | `molecules/empty-state` |
| `key` | `atoms/badge`, `atoms/kbd`, `atoms/skeleton`, `atoms/tooltip`, `atoms/typography`, `molecules/code-block`, `molecules/phone-input` |
| `keySmall` | `atoms/kbd` |
| `keyTiny` | `atoms/kbd` |
| `inlineCode` | `atoms/typography` |
| `optionChip` | `atoms/chip` |
| `optionTile` | `atoms/chip` |
| `tooltip` | `atoms/tooltip` |
| `event` | `organisms/calendar` |
| `bar` | `charts/shared` |
| `mark` | `charts/box-plot`, `charts/bullet-chart`, `charts/service-health-list`, `charts/stacked-bar`, `charts/waterfall-chart` |
| `cell` | `charts/bullet-chart`, `charts/histogram`, `charts/pie-chart`, `charts/radial-bar-chart`, `charts/service-health-list`, `charts/shared`, `charts/stacked-bar` |
| `candle` | `charts/candlestick-chart` |
| `chartTile` | `charts/treemap` |
| `heatCell` | `charts/heatmap` |
| `sparkTrack` | `charts/sparkline` |
| `funnelBar` | `charts/funnel-chart` |
| `listBar` | `charts/shared` |
| `stripBar` | `charts/bullet-chart`, `charts/service-health-list` |
| `sparkBar` | `charts/metric-breakdown`, `charts/sparkline` |
| `chartFlag` | `charts/shared` |
| `zoomControl` | `charts/shared` |
| `tableFrame` | `organisms/data-table` |
| `listFrame` | `molecules/stacked-lists`, `organisms/row-menu` |
| `gridTile` | `molecules/grid-lists` |
| `statTile` | `molecules/stats` |
| `statSwatch` | `molecules/stats` |
| `dragGhost` | `organisms/drag-drop` |
| `composer` | `organisms/chat` |
| `bubble` | `organisms/chat` |
| `bubbleTail` | `organisms/chat` |
| `assistantComposer` | `organisms/chat` |
| `assistantBubble` | `organisms/chat` |
| `navRow` | `organisms/sidebar`, `organisms/tabs` |
| `navRowCompact` | `organisms/sidebar`, `organisms/steps` |
| `rail` | `organisms/sidebar` |
| `floatingRail` | `organisms/filter-panel`, `organisms/sidebar` |
| `navBar` | `organisms/navbars` |
| `lane` | `organisms/board`, `organisms/drag-drop` |
| `laneCard` | `organisms/board` |
| `panel` | `molecules/card`, `organisms/data-table` |
| `stage` | `molecules/card` |
| `copyButton` | `molecules/code-block` |
| `cardSnippet` | `molecules/code-block` |
| `showcaseCard` | `molecules/card` |
| `heroStage` | `molecules/card` |
| `swatchBar` | `atoms/swatch` |
| `swatchStep` | `atoms/swatch` |
| `rampBar` | `atoms/swatch` |
| `iconCell` | `atoms/icon` |
| `stepMark` | `atoms/badge` |
| `terminalScreen` | `organisms/drawer` |
| `actionSheet` | `organisms/action-sheet` |
| `groupedList` | `atoms/radio`, `molecules/description-lists`, `molecules/stacked-lists`, `organisms/row-menu` |
| `searchField` | `atoms/input` |
| `chip` | `atoms/chip` |
| `sliderTrack` | `atoms/slider` |
| `snackbar` | `organisms/toast` |
| `navigationDrawer` | `organisms/drawer`, `organisms/sidebar` |
| `carouselItem` | `organisms/carousel` |
| `tabIndicator` | `organisms/tabs` |
| `sliderTrackInner` | `atoms/slider` |
<!-- @/generated -->

A nested surface is never rounder than what contains it. Where a control is smaller
than its platform's minimum touch target, the shape stays and the TOUCH area grows: the
kit measures the rendered control and extends it with hitSlop, so nothing moves. Every
kit control does this for itself (Button, an icon Button included, Chip, and Avatar, Card
and the list rows given `onPress`); a control the kit lacks belongs in the kit, which
gives it the same touch area, never in a Pressable of your own.

On iOS and Android that touch area follows two of React Native's own rules, which matter
when you lay small controls out. First, a touch area never reaches past a native ancestor
that does not contain it. A layout-only Row, Column or View is flattened away and never
stops it; a container that clips (overflow hidden, a scroll view's edge) stops it at its
edge; and one that paints, carries a testID or handles pointer events admits it only as
far as the layout it last recorded. The kit's own clipping views carry the touch area of
the controls inside them (the ripple clip first, which clips on Android), and the kit's
controls have their touch area from their first frame, so a view that hugs one records it.
Second, where two touch areas overlap, the later sibling takes the tap. Inside a component
the kit splits the gap between two of its own controls, so neither takes a tap inside the
other; between controls you place, it does not. In your own layouts leave at least twice
the extra touch area between two small controls (16 between two small icon Buttons on
Android, 8 on iOS).

## Elevation and depth

Three shadows, the hand-off's. In the light scheme each drops a soft ink shade and rings
the surface in a 1px hairline at 3%; in the dark scheme each lights the surface's top edge
with a 1px inset highlight over a black drop. `sm` (1px over 2px) rests under cards and
tiles, `md` (12px over 32px) under raised cards, menus and floating chrome, and `lg` (30px
over 80px) under overlays; `DEFAULT` is `sm` and `xl` is `lg`. The values below are the
light scheme's.

<!-- @generated:elevation -->
| Level | Value |
| --- | --- |
| `none` | `none` |
| `sm` | `inset 0px 1px 0px 0px rgba(255, 255, 255, 0),0px 1px 2px 0px rgba(17, 17, 24, 0.05),0px 0px 0px 1px rgba(17, 17, 24, 0.03)` |
| `md` | `inset 0px 1px 0px 0px rgba(255, 255, 255, 0),0px 12px 32px -10px rgba(17, 17, 24, 0.16),0px 0px 0px 1px rgba(17, 17, 24, 0.03)` |
| `lg` | `inset 0px 1px 0px 0px rgba(255, 255, 255, 0),0px 30px 80px -16px rgba(17, 17, 24, 0.25),0px 0px 0px 1px rgba(17, 17, 24, 0.03)` |
| `base` | `var(--shadow-sm)` |
| `xl` | `var(--shadow-lg)` |
<!-- @/generated -->

Cards rest at `sm`, raised cards at `md`, overlays at `lg`. iOS surfaces are
flat by convention and take no shadow at all.

## Motion and interaction

<!-- @generated:motion -->
| Token | Value |
| --- | --- |
| `--duration-fast` | `120ms` |
| `--duration-base` | `180ms` |
| `--duration-slow` | `280ms` |
| `--ease-standard` | `cubic-bezier(0.2,0,0,1)` |
| `--ease-out` | `cubic-bezier(0,0,0.2,1)` |
| `--ease-in-out` | `cubic-bezier(0.4,0,0.2,1)` |
<!-- @/generated -->

Canvas animates sparingly and functionally: entrance fades, an accordion height, a
skeleton shimmer, a spinner. Under `prefers-reduced-motion` every duration above goes
to zero, while information-bearing motion (a spinner, a determinate progress bar) is
left intact. Animate only `transform` and `opacity`, which is both the React Native
native-driver constraint and the only thing a compositor can do cheaply.

Press feedback is the platform's own: iOS dims, Material 3 ripples, the web tints. Every
interactive component ships hover, press, focus and disabled states, and a data-bearing
one ships loading and empty states as well.

## Responsive behavior

Canvas is authored desktop-first: size a component for the desktop case, then add the
variants that scale it down. A component never dictates its own width: it fills
(fields, cards, tables, charts) or hugs its content (buttons, badges, chips), and the
nearest layout container provides the bounds, a `Container` step, a Row child's `span`
or a `Grid` tile. There are three mechanisms, in order of preference.

1. **Intrinsic sizing**, which costs no JavaScript: fill or hug inside the bounds the
   parent provides, a `minWidth` floor plus wrapping. Correct on the first frame and on
   the server. Never give a component a fixed width, and never make a parent
   content-sized where a fill child must resolve: the child then tracks its own content.
2. **Container measurement**, for a component that changes layout: measure the
   component's OWN width with `useContainerBreakpoint`, `useMeasuredWidth` or
   `useContainerWidth`. A component cannot know whether it is on a phone or in a 320px
   panel on a desktop. The unmeasured first frame renders the `base` (desktop) variant,
   unless the component seeds it from the viewport bucket (`seedViewport`, or
   `useContainerWidth`'s px fallback); the seed waits for hydration, follows a
   `BreakpointOverride`, and the measured width wins once it lands.
3. **Viewport breakpoints**, for window-level chrome only: `useBreakpoint`,
   `useFormFactor`, `useResponsive`. Only the Sidebar and FilterPanel drawer modes, an
   overlay's phone sheet (Popover's) and app shells qualify.

The viewport tier is the window's width alone, on every platform. A breakpoint applies
at its width and below (`sm` 640, `md` 768, `lg` 1024, `xl` 1280, `2xl` 1536); the
form factor is phone at or below 640, tablet at or below 1024 and desktop above, so a
narrow desktop window is the phone tier and a landscape phone usually the tablet tier.
An unknown width (a server render, the frame before layout) is `base`, the desktop
variant; a server-rendered app that knows it is serving a phone passes ThemeProvider's
`ssrBreakpoint`. `BreakpointOverride` pins the tier for a preview or a test, not the
geometry: a drawer or sheet under it still lays out against the real window.

Viewport for the shell, container for the components, intrinsic wherever possible.

## Do's and Don'ts

**Do** pass a semantic boolean for every variation, and add one to the kit when the
variation you need is missing.

**Don't** restyle through `style`. There is no styling escape hatch: no `backgroundColor`,
`borderRadius`, `color`, `fontSize`, `opacity`, shadow or gradient override at a call
site, and no `margin`, `padding`, `gap` or absolute positioning to nudge a component
around. Reaching for one means the kit is missing a capability, and the fix is to add
that capability.

**Don't** hand-compose a widget out of a primitive and raw styles. A chip, a pill, an
identity row, an avatar stack, a "+N" counter, a divider: if the kit does not have it,
add it to the kit.

**Don't** put a label beside a control that has a text slot. Use the slot.

**Don't** branch on the platform to render different markup, or reach into the DOM to
get an effect working on the web alone. When React Native lacks a primitive the design
needs, build it cross-platform from `react-native-svg`, the `boxShadow` and `filter`
style props, or arithmetic.

**Don't** read the CSS layer on a native build. `styles/` is the web hand-off, including
the `--p-*` platform variables that let a browser paint all three looks. On iOS and
Android the skins are the implementation.

## Iteration guide

Change a colour in `styles/tokens/colors.css`, mirror the resolved sRGB hex into
`src/style/tokens.ts`, and run `bun run validate-tokens`, which compares the two sides
including the oklch conversion. Change a component's metrics in its `*.styles.ts` skin
and mirror them into `styles/tokens/platforms.css`. Then run `bun run designmd:gen` so
this document keeps up, and `bun test`, whose design-rule suites assert most of what is
written above.

## Known gaps

Three lists in the repository record what is known to be wrong, each entry with a
measurement rather than an adjective, and each checked in both directions so it cannot
go stale: `e2e/responsive/component-widths.e2e.ts` holds the pages that still overflow
their column at tablet width, `test/touch-target-coverage.test.ts` holds the controls
below their platform's touch minimum and how every other one reaches it, and
`e2e/a11y/components.e2e.ts` holds the accessibility findings that stand. A fourth,
`HANDOFF-PARITY.md`, tracks where the prop surface diverges from the design hand-off.
None of those four ships in the package; they are in the repository.
