Architecture

How Toucan splits tokens, styles, interactions, React primitives, patterns, and the CLI without duplicating ownership.

Package Roles

Toucan is a modular React design system built from small first-party packages. Each package owns one layer of the system, and higher layers depend on lower layers instead of copying source.

Tokens

@toucan-ui/tokens ships the default design-token preset and importable foundation CSS. Custom themes use the same raw, alias, system, and dark token tiers.

Styles

@toucan-ui/styles owns framework-agnostic .tcn-* component CSS, utility CSS, responsive templates, and the style manifest used by the CLI.

React

@toucan-ui/react owns accessible React primitives, React adapters, hooks, and the React ejection manifest. Component entries import their shared CSS.

Interactions

@toucan-ui/interactions owns framework-neutral reducers, machines, and DOM helpers for keyboard navigation, focus, disclosure, listbox, menu, and related behavior.

Types

@toucan-ui/types owns framework-neutral type vocabulary. React-specific prop types stay in @toucan-ui/react.

Patterns + CLI

@toucan-ui/patterns composes React primitives into larger UI sections. @toucan-ui/cli builds token CSS, runs diagnostics/init, and ejects local component source.

The Token Cascade

Tokens flow through three tiers with strict referencing direction: raw -> alias -> system. Each tier adds semantic meaning.

Raw Tokens

Primitive values with no semantic meaning. Colour scales, sizing steps, font stacks, shadows, opacity, and layout scales live here.

raw/color.json
{
  "color": {
    "blue": {
      "500": { "$value": "#3b82f6", "$type": "color" },
      "600": { "$value": "#2563eb", "$type": "color" }
    },
    "neutral": {
      "0": { "$value": "#ffffff", "$type": "color" },
      "900": { "$value": "#171717", "$type": "color" }
    }
  }
}

Alias Tokens

Semantic roles that reference raw values. The default preset provides neutral, functional aliases; branded themes override these slots to express a different visual identity.

alias/color.json
{
  "color": {
    "primary": { "$value": "{color.neutral.900}", "$type": "color" },
    "on-primary": { "$value": "{color.white}", "$type": "color" },
    "surface": {
      "default": { "$value": "{color.neutral.0}", "$type": "color" }
    }
  }
}

System Tokens

Component-specific tokens that reference aliases. System tokens handle structural properties such as sizing, spacing, radius, and font weight. Colour CSS usually references alias tokens directly so theme changes cascade without extra indirection.

system/button.json
{
  "button": {
    "radius": { "$value": "{radius.md}", "$type": "dimension" },
    "font-weight": { "$value": "{font.weight.medium}", "$type": "fontWeight" },
    "md": {
      "padding-x": { "$value": "{spacing.lg}", "$type": "dimension" },
      "height": { "$value": "{sizing.lg}", "$type": "dimension" }
    }
  }
}

Default CSS Path

For package-import usage, import the default foundation tokens once in your global CSS. Add the shared responsive rules when using responsive Grid or Flex props. Component CSS is imported by the React component entries you use.

globals.css
@import '@toucan-ui/tokens/default.css';
@import '@toucan-ui/styles/responsive/default.css';
tsx
// react/src/components/button/index.ts
import '@toucan-ui/styles/components/button/button.css';
export { Button } from './button';

CLI CSS Path

The CLI compiles custom token sets into a local toucan.css. That file contains foundation tokens, selected component CSS, utility CSS, and responsive rules from the same source packages.

bash
npm install -D @toucan-ui/cli
npx toucan build --tokens ./my-tokens --out ./css/toucan
globals.css
/* Custom theme import — compiled by `npx toucan build` */
@import './css/toucan/toucan.css';

Use --components button,input,select to build a smaller CSS file for a known component set. Without --components, the CLI includes the complete component CSS surface.

Full-Ownership Ejection

Package imports are the normal path. When you want local ownership, toucan eject <component> copies canonical React source plus the minimal local dependency closure into your app.

bash
npx toucan eject button
npx toucan eject select --out ./src/components --force

Ejected components do not import from @toucan-ui/react. Shared vendored dependencies live under _toucan, component CSS is copied next to the component, and app-level token CSS is still required once.

Component Contract Model

React primitives emit semantic HTML, ARIA attributes, data-* state hooks, and .tcn-* class names. Shared component CSS maps those hooks to token values.

html
<button
  class="tcn-button"
  data-variant="primary"
  data-size="md"
  aria-busy="false"
>
  Click me
</button>
css
.tcn-button[data-variant="primary"] {
  background-color: var(--color-primary);
  color: var(--color-on-primary);
}

.tcn-button[data-size="md"] {
  height: var(--button-md-height);
  padding: var(--button-md-padding-y) var(--button-md-padding-x);
}

Interaction Ownership

Interaction logic is not a theme or animation layer. The @toucan-ui/interactions package owns reusable behavior primitives: reducers, state machines, keyboard navigation, focus management, dismissal, and DOM helpers. React adapters in @toucan-ui/react wrap those primitives for React components.

This keeps behavior testable outside React while letting the React package own the public component API.

Monorepo Structure

text
packages/
  tokens/        -> default token presets + foundation CSS
  styles/        -> framework-agnostic component CSS + responsive templates
  types/         -> framework-neutral type vocabulary
  interactions/  -> framework-neutral behavior primitives + DOM helpers
  react/         -> React primitives, hooks, adapters, ejection manifest
  patterns/      -> 36 React patterns composed from primitives
  cli/           -> toucan build/init/info/doctor/eject

apps/
  docs/          -> documentation site + Wiz'rd theme configurator

Build order follows ownership: tokens and styles feed the CLI; interactions and types feed React; React feeds patterns and the docs app.