# Strata components (`@layers/ui`)

> Source of truth for per-component contracts: **the strata site** — every
> component has a page at `strata.layers.com/components/<slug>` (source:
> `apps/strata/src/component-docs/`) with a live demo, when-to-use, and the
> house rules that bind it. Coverage is ENFORCED: a build gate
> (`apps/strata/scripts/check-component-coverage.mjs`) fails the site build
> for any `packages/ui` component without a page. This doc covers the package
> mechanics and the cross-consumer rules.

## Package mechanics

- **Source-consumed.** `@layers/ui` has no build step — consumers transpile
  `src/` directly and Tailwind scans it (every consumer stylesheet needs
  `@source ".../packages/ui/src"`). Changes are live in every app on save.
- **Subpath imports preferred:** `import { Button } from '@layers/ui/button'`.
  The barrel (`@layers/ui`) exists but subpaths keep bundles honest.
- CSS that a component needs beyond Tailwind lives in
  `packages/ui/src/css/<component>.css` (media-card, media-player, stat-card,
  content-card) — new components should be self-contained Tailwind; a
  companion CSS file is only for keyframes or pseudo-selectors Tailwind can't
  express.
- Primitives are shadcn-derived (`base-maia` style) on `@base-ui/react`
  (Base UI). Radix is fully retired from this package: triggers take a
  `render` prop (never `asChild`), overlay anatomy is
  Portal > Positioner > Popup (Backdrop for scrims), and state hooks are
  `data-open`/`data-closed`/`data-popup-open`. Per-component migration
  notes live in `.migration/*.md`.

## Inventory (by kind)

**shadcn primitives (stock — structure per the shadcn website, never
modified):** Alert, AlertDialog, Avatar, Badge, Bubble, Button, ButtonGroup,
Calendar, Card, Checkbox, Combobox, Command, Dialog, DropdownMenu, Field,
Form, Input, InputGroup, InputOTP, Label, Marker, Message, MessageScroller,
Popover, Progress, RadioGroup, Select, Separator, Skeleton, Slider, Switch,
Tabs, Textarea, Toggle, ToggleGroup, Tooltip. (Stock `Sheet` is installed but
never used directly — use `WindowSheet` or `CanvasSheet`.)

**Chat surfaces compose the chat vocabulary, never hand-rolled equivalents:**
`MessageScroller` owns conversation scrolling (auto-follow, yield on
scroll-up, jump-to-latest — never a manual overflow container or
stick-to-bottom hook); `Message`/`Bubble` own rows and surfaces; `Marker`
owns dividers and system notes; the `shimmer` utility owns live-status text.
Composition rules: the vendored skill at
`.agents/skills/shadcn/rules/chat.md`. In web-v2, `ChatPane` owns the
scroller and `ChatEntryList` wraps rows in `MessageScrollerItem` — surfaces
never touch scrolling.

**Layers wrappers (exist because stock defaults didn't fit):**
CommandInline, DateTimePicker, HashtagTextarea, PhoneInput.

**Layers components:** the authoritative inventory is `packages/ui/src/`
(one file per component) rendered exhaustively at
`strata.layers.com/components` — the coverage gate keeps the two identical,
so a component absent from the site does not exist. Props and usage context
live on each component's page and in its source.

## Variants

Variants are `class-variance-authority` (`cva`) props on the component —
Button's `variant`/`size`, Badge's custom `success` variant, InputGroup and
TabsList's `size` ("default" 36px / "sm" 32px). **Extend via variants, never
a parallel component** — a `ComponentSmall` fork drifts from the original.
House variant additions are documented on each component's site page.

## Rules

1. **Never hand-roll what shadcn provides.** Before building any interactive
   element, check the shadcn website; if a component exists, install/use it
   stock. A hand-rolled lookalike is a defect even if pixel-identical.
2. **No custom Tailwind on shadcn compounds** — stock structure and spacing
   are law. Audit every `className` you added before reporting a surface
   done; the only exemptions are one-off layout constraints (a popover
   width) and skeleton sizing.
3. **Every `SelectItem` sits inside a `SelectGroup`** (the popover padding
   lives on the group) — no orphan items, even for a single group.
4. **Forms never wrap gap-laid-out children.** `PopoverContent`,
   `DialogContent`, `Card`, and `WindowSheetContent` space their DIRECT
   children with grid/flex gap — a `<form>` wrapping them becomes one child
   and collapses the spacing. The `<form>` goes inside the content region;
   the submit button links via `form={id}` from the footer. Bare `<form>`,
   zero Tailwind on it; spacing comes from `FieldSet`.
5. **Stock shadcn docs live on the website, nowhere else.** URL pattern:
   `https://ui.shadcn.com/docs/components/<slug>` (WebFetch it). The shadcn
   MCP is for `add` install commands only — its demo registry is stale. We
   document only what is custom to us; if an installed component drifts from
   the current website docs, update the component, never document the old
   behavior.
6. **New component = `packages/ui` first.** If more than one consumer could
   ever want it, it starts in the package, not in an app's `components/`
   directory. Add a catalog entry (see `generative-ui.md`) when Elle should
   be able to compose with it.