Components Reference
The 37 primitives in src/components/ui/, grouped by what they do rather than by the batch that built them. The contract every one follows is in Components.
The recurring theme is native-first: most of these are a platform feature with tokens on it, which is why so few ship any JavaScript.
Static primitives
Button · Input · Label · Textarea · Badge · Alert · Separator · Skeleton · Avatar
No JavaScript. Button is the config several other primitives reuse rather than redefining — PaginationLink and InputNumber’s steppers both compose it.
Card
Card with CardImage, CardHeader, CardTitle, CardDescription, CardAction, CardContent and CardFooter, each its own file in the same folder. variant and size variants.
Disclosure, navigation aids and status
Accordion (Item/Trigger/Content) · Tabs (List/Trigger/Content) · Tooltip · Breadcrumb (Item/Link/Page/Separator) · Pagination (Item/Link/Ellipsis) · Progress · Spinner
Accordion is <details>. Tooltip is CSS-only. Tabs is the only one here that ships a script, and it degrades to all-panels-visible without it.
Overlays and native form controls
Dialog (Trigger/Close/Header/Title/Description/Footer) · Sheet · Dropdown (Trigger/Menu/Item) · Select · Checkbox · Radio · Switch · Table (Header/Body/Footer/Row/Head/Cell/Caption)
Dialog and Sheet are native modal <dialog> — a Sheet is a Dialog pinned to an edge through a side variant. They share one delegated controller in _dialog.ts: openers carry data-dialog-open="<id>", closers data-dialog-close, with backdrop light-dismiss and native Escape. It binds once and survives view transitions, so it needs no astro:after-swap re-init.
The fade/scale, the per-side slide, the backdrop scrim and the modal scroll-lock (html:has(dialog:modal)) live in _overlay.css as real @starting-style and allow-discrete transitions that honour prefers-reduced-motion.
Dropdown is the native Popover API — popover="auto" plus popovertarget — so the menu renders in the top layer and is never clipped, with native light-dismiss, Escape and focus return. The shared _popover.ts controller positions it under its trigger, reflows on scroll and resize, adds arrow-key roving, and syncs aria-expanded, which is what flips the chevron.
Select is a native <select> reusing the shared _field look with a token chevron. For a searchable one, see ComboBox or AdvancedSelect.
Checkbox, Radio and Switch are native inputs styled appearance-none with peer and :checked. Switch hides its checkbox sr-only and drives a track and thumb off peer-checked. Zero JavaScript.
Table is static <table> styling in a scroll wrapper. Sorting and data-grid behaviour are out of scope.
Advanced form controls
Slider · InputNumber · ToggleCount (+ Value) · PasswordInput · PasswordStrength · ComboBox (+ Option) · AdvancedSelect · Searchbox (+ Item)
Slider is a native <input type="range"> styled through the range pseudo-elements, including Firefox’s ::-moz-range-progress. Zero JavaScript.
InputNumber is a native number input between two steppers; a small script drives stepUp() and stepDown() and disables a stepper at its bound.
ToggleCount is the Monthly/Annual-style pricing toggle built on Switch — every ToggleCountValue for={id} swaps between its min and max text when the toggle flips.
PasswordInput reuses the input field and adds a show/hide button that flips type while keeping aria-pressed and aria-label accurate. PasswordStrength is a four-segment meter driven by the rule-based scorePassword in password/strength.ts, checked by password/strength.test.ts.
ComboBox is a role="combobox" input over a filterable role="listbox"; arrow keys move the active option through aria-activedescendant and Enter commits.
AdvancedSelect is a searchable single or multi select backed by a real, visually-hidden native <select> so it submits with the form. It requires JavaScript — the zero-JS alternative is the native Select.
Searchbox is a ⌘K command palette that reuses the Dialog shell, filters its items, adds arrow-key navigation and binds the global shortcut.
Navigation and content
Nav · Marquee · MegaMenu (Trigger/Panel/Item) · List (+ Item)
MegaMenu is a Dropdown with a wide multi-column panel — the same reuse move as Sheet reusing Dialog. The panel is a Popover-API top layer placed by the same _popover.ts controller. Its trigger reuses the navLink config so it sits between NavLinks in a Nav, and the panel keeps natural Tab order rather than roving focus, because it is a grid of links rather than a role="menu". columns (1, 2 or 3) sets width and column count.
It is click-to-open only. Hover triggers are hostile to touch and keyboard users, and the file says so.
List covers the static list set: marker (none, disc, decimal — decimal renders an <ol>), orientation="horizontal" for the dot-separated inline list, and ListItem’s icon slot for checked lists. Flex is applied only when an icon is present, so disc and decimal markers survive. Zero JavaScript.
Marquee is the seamless-loop scroller; its keyframes live in tailwind-theme.css and --marquee-gap must match the flex gap between copies.
Motion
Reveal is the one primitive about motion — a reveal-on-scroll wrapper composing the owned animation catalog through the native scroll timeline, so it is zero-JS. See Motion.
Shared internals
Not primitives. The leading _ marks them internal.
| File | Role |
|---|---|
_client.ts |
onReady — the load plus astro:after-swap re-init contract every scripted primitive shares |
_dialog.ts |
the delegated Dialog/Sheet controller |
_overlay.css |
overlay transitions, scrim and scroll-lock |
_popover.ts |
placement and roving for Dropdown and MegaMenu |
_field.ts |
the shared form-field look, size and validation state |
_listbox.ts |
filterByText, nextIndex, createActiveDescendant for the filterable trio |
_Chevron.astro |
the one disclosure/select chevron glyph |
password/strength.ts |
scorePassword, with its own check |
What ships JavaScript
Worth knowing when you are auditing bundle size. Everything not listed here is zero-JS: Tabs, InputNumber, PasswordInput, ComboBox, AdvancedSelect, Searchbox, plus the two shared controllers (_dialog.ts for Dialog and Sheet, _popover.ts for Dropdown and MegaMenu) which bind once and survive view transitions.
ThemeToggle was removed
It shipped here and has been deleted. The site is light-only, so a manual override had nothing to switch between. It went out with the .dark block and BaseHead’s pre-paint script.
It is noted rather than silently dropped because it was the one primitive that was not purely additive — it paired with an edit to BaseHead.astro, so removing it was a two-file change. Re-adding dark mode means restoring all three together.
Where to see them
/examples/ui renders every primitive in every variant. Dev-only, noindex, no pages in a production build. Delete src/components/Sections/UiCatalog/ and src/pages/examples/ before launch — it costs no JavaScript but its demo classes sit in the shared stylesheet.