# Engineering (/docs/reference/engineering)

The component contract, repository boundaries, motion rules, and documentation parity.

Every component follows the contract below.

## Component contract

- Base UI is the canonical behavior layer for focus management, keyboard navigation, roving
  focus, dialogs, popups, collision handling, dismissal, and ARIA wiring. Calendar uses
  react-day-picker because Base UI has no calendar primitive.
- Components forward props and refs, support controlled and uncontrolled state where relevant,
  and allow element replacement when the underlying element may reasonably change.
- Variants live beside the component in a `.variants.ts` file and use CVA.
- State is exposed on the element through attributes such as `data-state`, `data-variant`, and
  `data-loading`, so styling and tests read the DOM.
- Components use semantic theme tokens by default. Direct palette ramps are reserved for
  status accents and fixed neutral control states, as documented in the root contract.
- Add `"use client"` when client APIs, hooks, context, or interaction require it. Table uses
  client context even though its presentation parts have no interactive state.
- A panel of rows (select, searchable menu, command palette) opens with a row already active:
  the current selection, or the most recent one when several are chosen, otherwise the first
  row. Typing moves it to the top match. In the selects, hovering moves the active row; in the
  command palette hover only highlights, and a press moves the selection. The pointer leaving
  the panel keeps the active row. It carries `data-panel-active`, or Base UI's
  `data-highlighted` where the primitive owns the highlight. `autoFocusFirstItem={false}` opts
  out for menu-like uses.

## Motion contract

Hover, focus, simple fades, and ordinary press feedback are CSS-first. Button and Toggle use
Motion for spring presses. Motion also handles retained exits such as Morph's outgoing content,
gestures, springs, and layout animation. Base UI popups can use CSS exits through their presence
lifecycle.

Transitions name their properties and prefer compositor-friendly motion. Collapsible's height
transition and Sidebar's shell dimensions and offcanvas position are accepted layout-motion
exceptions. CSS durations use the shared motion tokens; JavaScript duration and easing defaults
mirror those tokens and must be updated together. CSS overrides do not change JavaScript
animations, so reduced motion is handled separately in both.

## Shared loading indicators

Loading indicators are caller-supplied React nodes. `LoaderProvider` installs automatically
with a consuming component and reaches React portals. Component `loader` props override it;
existing custom pending icons override both. Omitted values inherit; `null` hides the indicator.
`AsciiLoader` is the fallback. Alternative loaders and their settings belong to the caller.

```tsx
import { LoaderProvider } from "@/lib/loader";
import { Button } from "@/components/ui/button";
import { ClassicLoader } from "@/components/ui/classic-loader";
import { MatrixLoader } from "@/components/ui/matrix-loader";

<LoaderProvider loader={<ClassicLoader speed={1} />}>
  <Button loading>Save</Button>
  <Button loading loader={<MatrixLoader />}>Custom</Button>
</LoaderProvider>
```

Install `classic-loader` and `matrix-loader` separately through the CLI to use this example. Library implementations use
`LoadingIndicator` from `registry/lib/loader`; the shared helper does not import Classic or
maintain a variant/speed API. The helper renders the node inside a fixed-size decorative wrapper;
the consuming component owns announcements.

## Repository boundaries

Three related surfaces serve different jobs:

| Surface | Meaning |
| --- | --- |
| Registry item | A source bundle installed with `shadcn add` |
| Export or subcomponent | An API bundled inside a registry item, such as `SelectSortTrigger` inside `select` |
| Demo entry | Interactive documentation for a complete item or one particular composition |

An exported helper does not automatically need another install item, and a demo does not define
the registry boundary.

```text
registry/     Portable library source
  lib/        Shared utilities and motion presets
  ui/         Installable component source
inspector/    Data-driven demos, settings, and schemas
stories/      Fumadocs demo adapter
content/docs/ Routed MDX documentation
app/          Next.js routes; (site)/ holds the overview and docs pages
components/   Site shell (rail, page frame) and landing figures
_incoming/    Temporary staging; never imported
registry.json Install metadata and dependency graph
public/r/     Built registry (pnpm registry:build), served at /r/<name>.json
```

The dependency direction is `app/content/stories` → `inspector` → `registry`. Portable code
must never import from the documentation or demo layers.

## Shared foundation

These registry items are installed transitively rather than shown as standalone component
pages:

| Item | Responsibility |
| --- | --- |
| `4af-theme` | Semantic colors, typography, radii, elevation, keyframes, and motion durations |
| `cn` | Class-name composition with `clsx` and `tailwind-merge` |
| `slot` | Small local helper for `asChild` composition |
| `motion` | Shared spring and easing presets |
| `loader` | Caller-supplied loading slots and provider defaults, with `AsciiLoader` as the fallback |
| `use-action` | Run, pending, outcome, and revert state shared by Action Button and asynchronous menu rows |
| `table-of-contents-scroll` | Active-entry centering, endpoint padding, and overflow-edge state for constrained contents rails |
| `table-of-contents-tracking` | Heading activation, visibility observation, and click-to-scroll settling shared by both contents variants |

Use the matching action component or menu row for standard async controls. For a custom control,
`useAction` provides the shared pending, outcome, and reset behavior directly.

`cn` uses Tailwind Merge 3 for Tailwind 4 utilities, including custom-property forms such as
`duration-(--duration-fast)` and `w-(--sidebar-width)`. The theme's `shadow-border`,
`shadow-raised`, `shadow-overlay`, and `shadow-overlay-drop` are registered as shadow sizes,
so a consumer's later `shadow-none` replaces them while independent shadow colors survive.

## Documentation parity

Every installable UI item has one component page containing its demo, settings, installation,
usage, and component-specific guidance. The generated [component catalog](/docs/reference/components)
and overview count read `registry.json` directly.

Pages load their demo entries through the shared adapter and use the shared Install and Usage
reference. Foundation items such as the theme, `cn`, `slot`, and motion presets install as
dependencies and have no demos of their own. Append `.md` to any documentation URL to read it as
Markdown.

The demo layer also contains example helpers for Action Button, Badge cycling, Command Palette,
Morph, Select fruit icons, Sidebar, and Table of Contents. They hold demo state, sample data, or
page composition and are not installable. `inspector/` holds demo infrastructure only and has
no routes of its own.

The site documents the library; `registry.json` and the source are authoritative.

## Working on the repository

```powershell
pnpm dev
pnpm typecheck
pnpm lint
pnpm test
pnpm registry:validate
pnpm registry:build
```

Never run `pnpm build` while `pnpm dev` is running. They share `.next`, and a production build
can remove CSS chunks that the development server is still serving.

When a component is added or renamed, update `registry.json`, its demo entry when it has one, and
its `content/docs/` page. When public API or behavior changes, update the component page's
API/notes and `lib/component-reference.ts`. Update `COMPONENTS.md` and the agent catalog when
the installable catalog or composition guidance changes. Navigation, the component count, and
the public catalog are derived from those sources; `registry.json` is the installable catalog,
so don't keep a second hard-coded list.