---
name: design-system-usage
description: Rules for building any screen or component in the FunkyLime design system — which component to reach for, permitted and forbidden variant combinations, layout and spacing rhythm, and what to do when nothing in the library fits. Use this before writing any UI code, adding a component, or composing a screen, and use it when reviewing generated UI for drift. Consult it especially when tempted to build something new, because the library usually already has it under a name you did not guess.
---

# Building inside the system

A generated screen should start inside the system, not beside it. That only happens
if the constraints are written down where the agent reads them — which is here.

The failure mode this skill exists to prevent: a screen that looks approximately
right, uses four hardcoded greys, reimplements a dropdown that already exists, and
takes two days to reconcile. It is faster to check the inventory than to fix drift.

## Before writing any UI

1. **Read `reference/component-inventory.json`.** It lists every component, its
   variants, its props, and — the part people skip — what each is *for*. Match on
   purpose, not on appearance. A `Callout` and a `Toast` look similar and mean
   different things.
2. **Check the token layer** for every value. See the `design-tokens` skill. No raw
   hex, no arbitrary px, no `text-[13px]`.
3. **Compose before you build.** Most "new" components are two existing ones in a
   layout primitive.

## Choosing between similar components

| Situation | Use | Not |
|---|---|---|
| Action that navigates | `Link` | `Button` styled as a link |
| Action that changes state | `Button` | `Link` with an onClick |
| Action with icon only | `IconButton` (accessible name required) | `Button` with no children |
| Persistent, in-page message | `Callout` | `Toast` |
| Transient, system-initiated message | `Toast` | `Callout` |
| Blocking decision | `Modal` | `Popover` |
| Supplementary, dismissible content | `Popover` | `Modal` |
| Choice among ≤ 5 visible options | `RadioGroup` | `Select` |
| Choice among > 5 options | `Select` | `RadioGroup` |
| Free text with suggestions | `Combobox` | `Input` plus a custom list |

The pairs above are the ones that actually get confused in review. When two
components look similar, the distinction is almost always *who initiated it* and
*whether it blocks*.

## Forbidden combinations

These render, and they are all wrong:

- `Button variant="ghost" tone="danger"` as the only affordance for a destructive
  action — destructive actions need weight; ghost gives them none.
- Two `Button variant="primary"` in one view. Primary means *the* action. Two primary
  buttons means the hierarchy was never decided.
- `Modal` inside `Modal`. If a modal needs a modal, the flow needs a page.
- `Toast` for anything the person must act on. Toasts disappear; requirements do not.
- `Input` without a `label`. A placeholder is not a label — it vanishes on focus,
  which is exactly when it was needed, and screen readers treat the two differently.
- `IconButton` without `aria-label`. It renders as an unnamed button.
- Nested interactive elements — a `Button` inside a `Card` that is itself clickable.
  Keyboard and screen reader users cannot resolve it.
- Any component with `style={{ ... }}` for anything but a genuinely dynamic value
  (a computed position, a measured width). Static overrides belong in the system.

## Layout

Use the layout primitives — `Stack`, `Inline`, `Grid`, `Box` — rather than ad-hoc
flex. They take `gap` from the spacing scale and cannot express an off-scale value,
which is the point.

- Vertical rhythm: `Stack` with `space.stack.*`
- Horizontal grouping: `Inline` with `space.inline.*`
- Container padding: `space.inset.*`
- Never a margin on a component for external spacing. Spacing belongs to the parent
  layout, not the child. A component that carries its own outer margin cannot be
  reused in a denser context, and that is how you end up with `<Card className="mb-0
  !mt-2">`.

## When nothing fits

Say so, and say it before building. The report should name:

1. What the screen needs to express.
2. Which components you checked and why each fell short.
3. Whether this looks like a **one-off** or a **missing pattern**.

That third judgement is the valuable one. A one-off is fine — build it locally,
document it as deliberate, move on. A missing pattern means the system has a gap, and
it should be filed as a system issue rather than solved privately in one screen. The
same gap solved privately in three screens is how a design system dies.

Do not silently invent a component that duplicates an existing one under a different
name. `Alert`, `Notice`, `Banner` and `Callout` are the same component, and a library
that contains all four is a library nobody trusts.

## Accessibility is part of the component, not a pass afterwards

Every component in the inventory ships with focus behaviour, keyboard handling and
semantics already. You inherit them by using the component. You lose them by
rebuilding it.

When composing, the remaining obligations are:
- Focus order matches reading order — achieved by DOM order, not `tabIndex`.
- Every interactive element has an accessible name.
- Focus is visible against every surface it can appear on.
- Target size ≥ 24×24 CSS px (WCAG 2.2 AA minimum), and prefer 44×44 for anything
  primary or touch-first.
- Colour is never the only carrier of meaning.

## Reviewing generated UI for drift

Check in this order, because the cheap checks catch most of it:

1. Any raw colour, spacing or font value → token violation.
2. Any physical direction property → run the `rtl-bilingual` linter.
3. Any component that duplicates one in the inventory.
4. Any forbidden combination from the list above.
5. Any missing state — loading, empty, error, disabled. Screens are usually
   generated happy-path only.
6. Any accessibility regression from composition.

Classify each finding as **build defect** (the code diverged from the system) or
**system gap** (the system had no answer and the code invented one). Track them
separately. Build defects get fixed in the PR; system gaps get fixed in the system,
and if you only ever log the first kind, the system never improves and the same
defect keeps arriving under new names.
