Components Reference
The complete inventory of src/components/ui/. Each entry lists what the folder exports, its variant axes with every option, the defaults, and any props beyond the native element’s own attributes. For the contract these all follow, see UI Components.
Every primitive additionally accepts the native attributes of its root element and a class that wins over conflicting base utilities. Every one exports its tv() config as <Name>Variants for composition. Anything marked script ships a small bundled ES module and re-initialises on astro:after-swap; everything else is zero-JS.
Layout and content
Card — Card, CardImage, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter
variant: default · elevated · outline · interactive (default default)
size: sm · md · lg (default md) — padding only, so a per-part override still merges cleanly.
Separator
orientation: horizontal · vertical (default horizontal)
Skeleton — no variants. Uses Tailwind’s built-in animate-pulse, with a motion-reduce: guard.
Table — Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption
No variants. Static <table> styling inside a scroll wrapper. Sorting and data-grid behaviour are deliberately out of scope.
List — List, ListItem
marker: none · disc · decimal (default none) — decimal renders an <ol>.
orientation: vertical · horizontal (default vertical) — horizontal is the dot-separated inline list.
ListItem takes an icon slot for checked lists; flex is applied only when an icon is present, so disc and decimal markers survive.
GrainyPanel — the fixed-dark decorative panel.
Props: as (element, default div), parallax (defaults to siteSettings.useAnimations). script — pointer-only eased grid parallax; static with no JS or under reduced motion.
Padding, radius and alignment are the caller’s via class; slotted content should sit in a relative z-10 wrapper.
Actions
Button
variant: primary · secondary · outline · outline-primary · dark-pill · ghost (default primary)
size: sm · md · lg · cta (default md)
icon: true drops the horizontal padding and squares the button per size.
Props: href — renders an <a> instead of a <button>.
CtaButton — the house money-CTA recipe: Button (with group) + RollText + an optional trailing icon that nudges right on hover.
Props: label (required — a string, not a slot, so RollText can split it at build time), href, tone (solid · outline, default solid), size (any Button size, default cta), icon (an IconName).
Not gated on useAnimations — it is a micro-interaction, not decoration.
RollText — per-character text roll on the nearest Tailwind group hover or focus-visible.
Props: text (required). Pure CSS, with motion-reduce:transition-none.
ThemeToggle — manual light/dark override, reusing the button config. The sun/moon flip is CSS-only via the dark: variant, so it is correct pre-paint with no flash. script — the click only: toggles .dark, persists localStorage("colorTheme"), syncs aria-pressed.
Form controls
Input
size: sm · md · lg (default md)
state: default · error · success (default default)
Textarea — same two axes and defaults as Input.
Label
size: sm · md · lg (default md)
Select — a native <select> with a token chevron.
size: sm · md · lg (default md)
state: default · error · success (default default)
Checkbox, Radio, Switch — native inputs styled appearance-none with peer and :checked. No variants, zero JS. Switch hides its checkbox sr-only and drives a track and thumb from peer-checked.
Slider — a native <input type="range"> styled through the range pseudo-elements.
size: sm · md (default md)
InputNumber — a native <input type="number"> between two steppers that reuse the button config. Takes the Input variants. script — drives stepUp() / stepDown() and disables a stepper at its bound.
ToggleCount — ToggleCount, ToggleCountValue. The Monthly/Annual pricing toggle, built on Switch.
ToggleCount props: id (required), offLabel, onLabel.
ToggleCountValue props: for (required), min, max — swaps between the two strings when the toggle flips.
PasswordInput / PasswordStrength — the input reuses the input field and adds a show/hide button (script: flips type, keeps aria-pressed and aria-label accurate). The meter is four segments driven by the rule-based scorePassword, also exported from the folder along with its Strength type.
PasswordStrength props: for (required). size: sm · md · lg (default md).
ComboBox — ComboBox, ComboBoxOption. An autocomplete: a role="combobox" input over a filterable role="listbox". Arrow keys move the active option via aria-activedescendant; Enter commits. Takes the Input variants.
Props: emptyText. ComboBoxOption props: value, label. script
AdvancedSelect — a searchable single or multi select backed by a real visually-hidden native <select>, so it submits in a form. The styled trigger reuses the shared field look.
size: sm · md · lg (default md) · state: default · error · success (default default)
Props: options (required), name, multiple, search, placeholder, searchPlaceholder, countText, emptyText.
script — requires JavaScript. The zero-JS alternative is the native Select.
Searchbox — Searchbox, SearchboxItem. A ⌘K command palette reusing the Dialog shell.
Props: id (required), label, placeholder, emptyText, shortcut. SearchboxItem props: href.
script — filters items, adds arrow-key navigation, and binds the global chord through the shared _hotkey.ts owner.
Overlays
Dialog — Dialog, DialogTrigger, DialogClose, DialogHeader, DialogTitle, DialogDescription, DialogFooter
A native modal <dialog>. DialogTrigger takes for (the dialog’s id) and the Button variants. Openers carry data-dialog-open="<id>", closers data-dialog-close; Escape is native. script — one delegated controller (_dialog.ts) that binds once and survives view transitions.
Sheet — Sheet, SheetTrigger, SheetClose. A Dialog pinned to an edge.
side: right · left · top · bottom (default right)
Reuses Dialog’s trigger, close and content parts. Enter/exit transitions, the backdrop scrim and the modal scroll lock live in _overlay.css as real @starting-style + allow-discrete transitions that honor reduced motion.
Dropdown — Dropdown, DropdownTrigger, DropdownMenu, DropdownItem
The native Popover API, so the menu renders in the top layer and is never clipped. DropdownTrigger takes for; DropdownMenu takes id — they must match. DropdownItem takes an optional href and renders an <a> when present.
script — the shared _popover.ts controller positions the menu, reflows on scroll and resize, adds arrow-key roving, and syncs aria-expanded.
Tooltip — CSS-only.
side: top · bottom · left · right (default top)
Props: text (required).
Navigation
Nav — Nav, NavItem, NavLink
Nav orientation: horizontal · vertical (default horizontal)
NavLink variant: underline · slide · pills · segment (default underline); href required.
The active state is the caller’s job and is modelled with aria-current — page on the link that is the route, true on a mega-menu trigger whose panel holds it. Both spellings get the active look.
MegaMenu — MegaMenu, MegaMenuTrigger, MegaMenuPanel, MegaMenuItem
MegaMenuPanel columns: 1 · 2 · 3 (default 3) — sets both width and column count.
MegaMenuTrigger takes for and reuses the navLink config so it sits between NavLinks. MegaMenuItem is the rich link: href required, plus icon and description slots.
Click-to-open only — hover triggers are hostile to touch and keyboard, which the file says in a ponytail: note. The panel keeps natural Tab order (it is a grid of links, not a role="menu"). script — the same _popover.ts controller as Dropdown.
Breadcrumb — Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator
No variants. Pair it with getBreadcrumbSchema from the same trail array — see SEO & Structured Data.
Pagination — Pagination, PaginationItem, PaginationLink, PaginationEllipsis
PaginationLink props: href, isActive. It reuses the button config rather than defining its own.
Tabs — Tabs, TabsList, TabsTrigger, TabsContent
TabsTrigger props: value (required), selected. TabsContent props: value (required).
script — the only Tier 2 primitive with one. Degrades to all panels visible.
Accordion — Accordion, AccordionItem, AccordionTrigger, AccordionContent
No variants. Built on <details> and <summary>, so it is zero-JS and open/closed state is the browser’s own.
Feedback and display
Alert
variant: info · success · warning · error (default info)
Badge
variant: primary · secondary · muted · outline · info · success · warning · error (default primary)
Avatar
size: sm · md · lg (default md)
Props: src, alt. Renders an <img> when src is set, otherwise the slot — which is how fallback initials work.
Progress
size: sm · md · lg (default md)
Props: value (default 0), max (default 100). Clamped to 0–100% and given real role="progressbar" ARIA values.
Spinner
size: sm · md · lg (default md)
Props: label (default “Loading…”, used as the aria-label on a role="status").
CountUp — a number that counts up when it scrolls into view.
Props: value (required), prefix, suffix, decimals, durationMs (default 1600), as, animate.
script. Its formatting logic is covered by count-up/format.selfcheck.ts.
Marquee
direction: left · right · up · down (default left)
speed: slow (60s) · normal (30s) · fast (15s) (default normal)
Props: pauseOnHover (default true), repeat (default 4 — copies laid side by side to fill the viewport; at least 2 are needed for the loop to close).
Keyframes live in tailwind-theme.css beside the --marquee-duration and --marquee-gap knobs they read. Carries motion-reduce:animate-none.
Motion
Reveal — reveal-on-scroll, driven by the native scroll timeline. Zero-JS.
animation: fade-in · fade-in-up · fade-in-down · fade-in-left · fade-in-right · zoom-in · blurred-fade-in · slide-up-fade · bounce-fade-in · flip-in-x · flip-in-y (default fade-in-up)
range: entry · cover · contain · gradual · moderate · brisk · rapid (default entry)
Props: as (the wrapper needs a box — display: contents would break the timeline), animate (per-call override of siteSettings.useAnimations).
SplitReveal — staggered text reveal, split at build time.
Props: text (required), as, split (words · lines, default words), staggerMs (100 for words, 150 for lines), risePct (120 / 100), durationMs (700), ease, animate. script
StaggerReveal — batched reveal for a group of sibling elements.
Props: as, staggerMs (default 120), riseY, durationMs, ease, animate. script
Children crossing into view together form one batch, cascaded by staggerMs.
All three degrade to visible static content with no JavaScript or with useAnimations off, and all three honor prefers-reduced-motion. See Motion & Animation.
Internal shared modules
Not primitives — no folder, no index.ts, a leading underscore. Listed because you will see them imported and should use them rather than reinventing their job.
| Module | Owns |
|---|---|
_client.ts |
onReady(selector, wire) — the load + astro:after-swap contract and the once-per-element WeakSet guard |
_scroll.ts |
onScrollFrame(el, update) — one rAF-coalesced scroll/resize pass for the whole site |
_hotkey.ts |
onCommandK(el, open) — the single owner of ⌘K / Ctrl-K |
_dialog.ts |
the delegated Dialog/Sheet open/close controller |
_popover.ts |
the Dropdown/MegaMenu placement controller |
_listbox.ts |
filterByText, nextIndex, createActiveDescendant for the filterable trio |
_field.ts |
fieldBase and fieldState — the shared text-field look and validation states |
_overlay.css |
overlay enter/exit transitions, the backdrop scrim, the modal scroll lock |
_Chevron.astro |
the one disclosure/select chevron glyph |
Four of them carry runnable checks: _hotkey.selfcheck.ts, _listbox.selfcheck.ts, count-up/format.selfcheck.ts and password/strength.test.ts. pnpm test runs them.
Seeing them
/examples/ui renders every primitive in every variant, in eight catalog groups. It is dev-only. Open it in light and dark — that is the check the type system cannot do for you.