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.tsfile and use CVA. - State is exposed on the element through attributes such as
data-state,data-variant, anddata-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'sdata-highlightedwhere 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.
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:
An exported helper does not automatically need another install item, and a demo does not define the registry boundary.
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>.jsonThe 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:
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
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
pnpm dev
pnpm typecheck
pnpm lint
pnpm test
pnpm registry:validate
pnpm registry:buildNever 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.