Primitives

Every shared primitive in packages/ui, rendered across its variants and states. Use it to eyeball a base-primitive change — or a shadcn upgrade — against the whole surface at once. See DESIGN.md § 5 for which primitives are forked and why.

CFX signatures

  • CurrencyAmount
  • CopyToClipboard
  • MaskedValue

The three patterns that make a surface read as CFX rather than as shadcn with tokens: cents set smaller than the dollars, a ledger ID rendered verbatim beside a copy control, and a sensitive value revealed in place. DESIGN.md § 3 and § 5.

CurrencyAmount

The Cents-As-Smaller-Figure Rule, at all four sizes. An inbound amount takes text-success; a debit carries a true minus sign rather than a hyphen.

CopyToClipboard and MaskedValue

The Verbatim-ID Rule: a public ID renders in full, in mono, with copy beside it — never abbreviated, never behind a tooltip. MaskedValue is the same control with the display redacted until hover or focus; the full value is what gets copied either way.

Button

  • Button

Six variants, then the states that carry the CFX divergence: the full-alpha focus ring and the disabled treatment that drops the fill rather than dimming it.

Variants

default
Every variant at all four sizes
default
secondary
outline
ghost
link
destructive

Disabled

Loading

isLoading sets disabled and aria-busy; not upstream.

Icon sizes

Badge

  • Badge

Tags and permanent metadata only. Status is never a badge — see the Containers table below and The Status-As-Text Rule in DESIGN.md § 2. badgeVariants is exported by upstream and kept exported here.

Tags

ACHUS wireSandboxReward program

What a Badge is for: a rail, a plan tier, an environment — metadata that does not change as a record moves through its lifecycle.

Variants

defaultsecondaryoutlinedestructiveghostlink

Inputs

  • Input
  • Textarea
  • Label
  • Switch
  • Checkbox
  • RadioGroup
  • Toggle
  • ToggleGroup

Resting, invalid and disabled side by side — aria-invalid is what drives the destructive ring, so it has to be set to be seen.

Input

Textarea

Switch

The before:-inset-* hit area is invisible; tab to it and check the ring.

Checkbox and RadioGroup

Toggle

ToggleGroup

Select

  • Select

Placeholder, a committed value, grouped options with a disabled item, and both trigger sizes. The trigger draws its own border, so it takes the bordered half of The Focus-Contrast Rule — border-ring at full alpha plus the 3px halo.

Placeholder, grouped

Placeholder text renders in muted-foreground off data-[placeholder]; the disabled item keeps its row and drops to 50%.

Selected, small and disabled

size drives the trigger height only: sm is h-8, default h-9, matching Button's own step.

InputOTP

  • InputOTP

The primitive most likely to come back from a regen broken: rules/shadcn.md records that its <div role="separator"> needs three biome-ignore comments re-added by hand after every overwrite. Empty, partially filled, and the separator between the two groups of three.

Empty

Tab into it — the active slot is what carries the ring, and the caret is drawn rather than native.

Partially filled

4
2
9

value is pinned with no onChange, so the specimen holds the half-entered state instead of being typed away.

Disabled

4
2
9
8
1
5

Feedback

  • Alert
  • Skeleton
  • Spinner

Alert, Skeleton and Spinner — the surfaces a loading or failed state lands on.

Alert

Skeleton

Spinner

Loading

Containers

  • Card
  • Tabs
  • Table
  • Separator

Card, Tabs, Table and Separator — the layout register the dashboards build pages from. The status column is the rule in practice: plain inline text coloured by role, across all three of its colours.

Card

Card
Full composition: header, content and footer.
Body copy sits here.

Tabs

First panel.

Separator

A hairline on the border token, so it disappears into the surface rather than drawing a line across it.

Table

NameStatus
Acme HoldingsActive
Northwind TradingPending
Grey Harbour LtdFailed

The Status-As-Text Rule (DESIGN.md § 2): status in any list is inline plain text — text-destructive for terminal failure, text-warning for pending review, text-muted-foreground otherwise. No chips, no outline pills, no side stripes.

Accordion

  • Accordion

Both root types, each with an item open at rest so the collapsed and expanded treatments sit side by side. single collapsible closes the open item when it is clicked again; multiple keeps every open item open.

type="single" collapsible

Same business day when submitted before the 3pm ET cut-off.

One item at a time. The chevron rotates off data-state, and the trigger is borderless — it takes the halo recipe upstream ships.

type="multiple"

Same business day when submitted before the 3pm ET cut-off.

At quote time, from the fee rule version in force that instant.

Two open at once, and the last item keeps no bottom border.

Overlays

  • Popover
  • DropdownMenu
  • DropdownMenuTriggerButton

Popover and DropdownMenu, plus the two CFX modules that live beside their primitives so those stay overwritable.

Popover

PopoverHeader / Title / Description are upstream, not CFX.

DropdownMenu

DropdownMenuTriggerButton is CFX, in its own module.

Tooltip

  • Tooltip
  • QuickTooltip

All four sides off the raw composition, the QuickTooltip wrapper that carries its own provider — what CopyToClipboard uses — and one left open at rest so the inverted content chip and its arrow are visible without a pointer.

Sides

Hover or tab to each. Radix flips a side that would collide with the viewport edge, so at narrow widths these will not all point the way they are labelled.

QuickTooltip

content plus children, provider included — one import instead of four.

Open at rest

open is pinned with no onOpenChange, so the content treatment is on screen without a pointer. defaultOpen is not enough — Radix closes a tooltip as soon as anything above its trigger scrolls, which is every scroll down this page.

Avatar

  • Avatar
  • AvatarGroup

Avatar, AvatarBadge, AvatarGroup and AvatarGroupCount all ship upstream — kept here so a future overwrite can be checked against them.

Avatar and AvatarBadge

CF

The badge is positioned off the avatar's own box, so it moves with the size rather than with a wrapper.

AvatarGroup and AvatarGroupCount

ABCDEF
+4

Overlapped by a negative margin, each ring drawn in the background token so the stack reads in both themes.

Command

  • Command
  • CommandLoading

The cmdk surface, with CommandLoading — the CFX pending row that now lives beside command.tsx. Loading and results are mutually exclusive states, so they are two specimens rather than one.

Loading

No CommandGroup and no CommandEmpty beside it: cmdk renders the empty row whenever the filtered count is zero, which is exactly what a list still fetching has.

Loaded

Type something the three rows do not match to reach CommandEmpty.

Calendar

  • Calendar

Reverted to upstream source. Its only residual divergence is import order and the bracket-access rewrites noPropertyAccessFromIndexSignature requires.

Single month

September 2026

month and today are pinned to the fixture date. This page is statically prerendered, so an unpinned calendar renders the build machine's month and marks the build day as today — a baseline that changes on a rebuild nobody asked for.