Packages

UI

A typed token floor, --cs-* OKLCH tokens, one light and one dark theme.

@caisson/ui is the typed token floor and the styled component kit built on it. It ships a set of --cs-* custom properties in OKLCH, delivered as exactly two themes (one light, one dark) plus a catalog of components (Button, Card, Hero, Terminal, DataTable, and more) that read those tokens. This docs site renders on those same tokens and components.

The contract

You re-skin by swapping token values, never by forking a component. A component reads a token; it never hard-codes a hex value, so a brand change is a set of new variables, not a patch across the component tree.

:root {
  --cs-bg: oklch(99% 0 0);
  --cs-fg: oklch(20% 0 0);
  --cs-accent: oklch(62% 0.19 256);
}

/* Re-skin = new values here. Components read the tokens; they never fork. */

API reference

@caisson/ui has three entry points: @caisson/ui/tokens (the raw token contract), @caisson/ui/theme (compose and apply a theme at runtime), and @caisson/ui/components (the styled kit, transpiled from raw .tsx: set transpilePackages: ["@caisson/ui"] in next.config). ./styles/tokens.css and ./styles/base.css are exported for direct <link>/@import use outside a bundler that resolves .css imports.

Tokens, @caisson/ui/tokens

const foundation: Foundation; // type scale, weight, line-height, tracking, 4px space,
// radius, the rem breakpoint ladder, motion, elevation — frozen `as const`

const darkTheme: SemanticTheme; // locked default dark palette
const lightTheme: SemanticTheme; // locked default light palette
const functional: FunctionalTokens; // @deprecated back-compat alias of functionalDark
const functionalDark: FunctionalTokens; // dark-tuned variant
const functionalLight: FunctionalTokens; // light-tuned variant
const fonts: { sans: string; mono: string }; // locked stacks, CSS-var-wrapped with a literal fallback

function semanticThemeToCssVars(theme: SemanticTheme): Record<string, string>;
function semanticCssLines(theme: SemanticTheme, indent?: number): string[];
const SEMANTIC_VAR_NAMES: ReadonlyArray<readonly [keyof SemanticTheme, string]>;

Every SemanticTheme carries 15 OKLCH roles: bg, surface1/surface2, border/borderStrong, fg/fgMuted, accent/accentHover/onAccent/accentTint, focus, link, glowAccent (the accent instrument-glow shadow), and scrim (the modal/drawer backdrop veil). semanticThemeToCssVars/semanticCssLines are the single --cs-* mapping consumed by both the build-time CSS generator and the runtime theme API below, so the two can't drift apart.

Theme, @caisson/ui/theme

function createTheme(options?: {
  preset?: string; // "caisson" (default) | "pressure" | "bulkhead" | a registered custom id
  overrides?: ThemeOverrides; // partial per-mode token overrides, Zod `.strict()`-validated
}): Theme; // { id: string; dark: SemanticTheme; light: SemanticTheme }

function applyTheme(
  theme: Theme,
  options?: { target?: Document; styleId?: string },
): void;
function themeToCssText(theme: Theme): string;
function themeToCssVars(theme: Theme): {
  dark: Record<string, string>;
  light: Record<string, string>;
};

function registerPreset(preset: ThemePreset): void;
function getPreset(id: string): ThemePreset | undefined;
function listPresets(): readonly ThemePreset[];
const DEFAULT_PRESET_ID: "caisson";

createTheme throws on an unknown preset id (the error names the registered ones) and on an override with an unrecognized key or an empty-string token value, every token value is also denylist-validated against { } < > ;, because applyTheme/themeToCssText interpolate it raw into a <style> block. registerPreset is append-only by id: re-registering an existing id with a different shape throws instead of silently swapping every consumer already resolved to it. applyTheme is a no-op with no document (SSR), render themeToCssText() into a <style> tag server-side instead for a flash-free first paint.

Components, @caisson/ui/components

Every component follows one recipe: Radix only where real behavior is needed (asChild polymorphism in Button/FormField: Dialog's focus trap comes from the native <dialog> element instead), co-located CSS reading only var(--cs-*), variants as data-* attributes (no theme branching in JS), and, where the component exposes one, forwardRef on its single DOM root. Button is the reference shape:

type ButtonVariant = "primary" | "ghost";
type ButtonSize = "sm" | "md";

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: ButtonVariant;
  size?: ButtonSize;
  asChild?: boolean; // render as the single child element (Radix Slot) — e.g.
  // <Button asChild><Link href="/x">…</Link></Button>
  children?: ReactNode;
}

const Button: ForwardRefExoticComponent<
  ButtonProps & RefAttributes<HTMLButtonElement>
>;

Marketing & layout: purely presentational, server-safe (no "use client"):

ComponentPropsBehavior
SectionSectionPropsEyebrow/title/lede block with a toned band (tint/surface) and an optional flush hairline.
HeroHeroPropsSplit marketing header, eyebrow/title/lede/ctas/credentials plus an artifact slot (typically a Terminal).
CardCardPropsPresentational panel, no navigation or toggle behavior of its own.
FaqFaqProps, FaqItemNative <details>/<summary> disclosure list.
FeatureGridFeatureGridPropsResponsive feature-card grid.
TerminalTerminalPropsFramed evidence artifact, chrome bar + monospace body.
CodeBlockCodeBlockProps<pre> code surface; frame wraps it in Terminal chrome instead.
StatusChipStatusChipPropsGlyph + label status pill, never color-alone.
CredentialStripCredentialStripPropsCompliance-framework name strip.
BundleCardBundleCardPropsFeatured bundle link card.
SkuMatrixSkuMatrixProps, SkuMatrixRowFeature-by-bundle comparison matrix.
RevealRevealPropsFade-up-once scroll reveal via IntersectionObserver.
MobileBuyBarMobileBuyBarPropsSticky mobile price + purchase bar.
ThemeToggleThemeTogglePropsDark/light pin toggle. Pairs with THEME_STORAGE_KEY + themeInitScript (inline in the root layout <head>) for a flash-free first paint that otherwise follows the OS.

Dashboard shell & data: the buyer-dashboard data-app surface:

ComponentPropsBehavior
AppShellAppShellProps, AppShellNavItemCollapsible sidebar + top bar shell; becomes an off-canvas drawer below the md breakpoint.
DataTableDataTableProps<T>, DataTableColumn<T>Sortable/filterable/paginated table; renders LoadingState/EmptyState internally for those states.
PaginationPaginationPropsAccessible page selector. paginationRange(page, pageCount, siblingCount?) is the pure windowing function it renders from.
SelectSelectProps, SelectOptionStyled wrapper over the native <select>.
CopyFieldCopyFieldPropsRead-only value with a one-click clipboard copy; secret masks it until copied.
DetailListDetailListProps, DetailItemSemantic <dl> term/value rows; columns or stacked layout.
MetricStatMetricStatPropsLabel/value/hint stat tile with a tone-tinted icon.
MoneyCellMoneyCellPropsRenders an integer cent/credit value via formatMoneyCellValue(value, unit?, opts?), which throws on a non-safe-integer input.
LedgerList / LedgerRowLedgerListProps / LedgerRowProps, LedgerEntrySigned credit-ledger transaction rows. formatLedgerTimestamp(iso) formats the entry time.
StatusPillStatusPillProps, EntitlementStatusactive/expired/revoked/pending lifecycle pill.
EmptyStateEmptyStatePropsThe calm "nothing here yet" affordance, DataTable's default empty render.
ErrorStateErrorStatePropsThe calm failed-fetch affordance.
LoadingStateLoadingStatePropsSkeleton standing in for a table/stat/list/block layout, sized to avoid layout shift.
FormFieldFormFieldPropsLabel + control + helper/error, wired to the control via Radix Slot.

Overlays & feedback:

ComponentPropsBehavior
DialogDialogPropsModal or edge-drawer on the native <dialog>: showModal() gives focus-trap, inert background, Escape-to-close, and focus-return for free.
ConfirmDialogConfirmDialogPropsConfirm/cancel prompt on Dialog; tone: "danger" tints the confirm action for a destructive step.
Toast / ToastRegionToastProps / ToastRegionPropsTransient notification. danger/warning announce assertively (role="alert"); info/success politely (role="status"). Mount toasts inside one ToastRegion per stack.

Form & interactive primitives: hand-rolled, zero extra dependency:

ComponentPropsBehavior
BadgeBadgePropsCompact solid-fill tag, text only, no icon/dot slot.
CheckboxCheckboxPropsReal <input type="checkbox"> under a CSS-drawn check mark.
RadioRadioPropsReal <input type="radio"> under a CSS-drawn dot.
SwitchSwitchProps<button role="switch" aria-checked> for an immediate-effect setting (not a form value).
AccordionAccordionProps, AccordionItemNative <details>/<summary> list; type="single" gets mutual exclusivity from <details name>.
TabsTabsProps, TabItemWAI-ARIA tablist, roving tabindex, arrow-key + Home/End navigation, disabled tabs skipped.

Icons:

ExportSignatureBehavior
IconForwardRefExoticComponent<IconProps & RefAttributes<SVGSVGElement>>Renders a Lucide glyph by name, or a bespoke glyph registered via registerIcons.
registerIcons(glyphs: Partial<Record<RegisteredIconName, IconGlyph>>) => voidPopulates the bespoke-glyph registry. The open kit ships no bespoke art, the consuming app supplies it.