Containers
The container family covers the box-shaped surfaces that wrap other content: cards, accordions, collapsibles, list items, steppers, scrollable regions, and separators. They live under shell/ because they hold the page chrome together rather than collecting input or driving an action. Their visual contract is a small set of values: a 14 px corner radius (the card Semantic radius tokens.css-token:vueda-card-radius), a 1 px Color palette: light tokens.css-token:border, the Color palette: light tokens.css-token:card fill, and the Semantic shadow tokens.css-token:vueda-shadow-card drop.
This page is the visual contract the default theme guarantees. Use it as the target spec when you re-skin: every cell shown here should still read as the same surface after a customization, even if its color, radius, or density shifts. If a cell breaks, the change has crossed from skin into design language.
For the mechanics of overriding any of this, see Customize VUEDA Appearance. In brief: values (color, dimension, duration) belong in CSS tokens; compositions (class arrangements, state recipes) belong in theme keys.
One structural decision worth flagging up front. Card uses vertical-only padding on its root (py-6, no px) and lets each child apply its own px-6. The result is that a child can opt out of the gutter (a table that needs to bleed to the edge) by setting its own padding to zero, and a header that wants a divider rule can add border-b and pick up pb-6 automatically via [.border-b]:pb-6. Header / content / footer divisions stay self-similar without per-position wrappers.
Card
Card is the primary surface for presenting a single object's information. It is a flex column with gap-6 between regions and seven theme keys: the shell (Card) plus six region keys (CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter). The header is a two-row CSS grid; when a CardAction child is present, the grid switches to [1fr auto] so the action lands flush right and spans both rows.
Token surface: Color palette: light tokens.css-token:card (fill), Color palette: light tokens.css-token:card-foreground (text), Color palette: light tokens.css-token:border (stroke), Semantic radius tokens.css-token:vueda-card-radius (corners), Semantic shadow tokens.css-token:vueda-shadow-card (drop).
Accordion
Accordion is a stack of disclosure rows. Each row is an AccordionItem (a 1 px bottom border, suppressed on the last item) with a header trigger (AccordionTrigger) and a collapsible panel (AccordionContent). The chevron rotates 180° via [&[data-state=open]>svg]:rotate-180 on the trigger; the panel animates with the accordion-up / accordion-down keyframes inlined at the base layer.
Theme keys: Accordion, AccordionItem, AccordionTrigger, AccordionContent.
Collapsible
Collapsible is a logic primitive, not a styled surface. All three of its theme keys (Collapsible, CollapsibleTrigger, CollapsibleContent) ship empty in the default theme; the component contributes show / hide behavior and data-state attributes, and the consumer brings the chrome. Use it when you want disclosure without the FAQ-style row treatment of Accordion.
Item / ItemGroup
Item is a flexible row primitive used for list-like layouts where each row shows a heading, an optional media slot, and optional trailing actions. The shell (Item) takes a variant (outline, muted, default transparent) and a size (sm, default md). Sub-keys carve the row into regions: ItemMedia (left icon / image), ItemContent (title + description), ItemActions (right buttons), with ItemHeader and ItemFooter for full-width auxiliary rows. ItemGroup is a vertical flex column; ItemSeparator drops a 1 px rule between rows.
The default Item is bg-transparent; an is-link (or anchor) item raises to bg-accent/50 on hover via [a]:hover:bg-accent/50 on the root.
Stepper
Stepper renders a sequence of milestone indicators, optionally with title and description, separated by a connecting bar. The state cube is encoded in data-state on the StepperItem: active flips the indicator to Color palette: light tokens.css-token:primary / Color palette: light tokens.css-token:primary-foreground, completed flips it to Color palette: light tokens.css-token:accent / Color palette: light tokens.css-token:accent-foreground, and the unset (future) state shows muted-foreground at 50% opacity. data-disabled propagates opacity 50 across the indicator and the separator that follows.
Theme keys: Stepper, StepperItem, StepperTrigger, StepperIndicator, StepperTitle, StepperDescription, StepperSeparator.
ScrollArea
ScrollArea wraps a fixed-height region and replaces the platform's native scrollbar with a 10 px track plus a bg-border thumb. The component renders both axes; whichever isn't needed stays inert. The viewport is what receives focus, so the ring outline lands inside the parent's overflow clip.
Theme keys: ScrollArea (root + viewport), ScrollBar (track + thumb). The thumb reads from Color palette: light tokens.css-token:border.
Separator
Separator is a 1 px rule. It picks an axis from orientation and stretches along the cross axis: a horizontal separator is h-px w-full, a vertical one is w-px h-full. Both modes read color from Color palette: light tokens.css-token:border. The vertical mode requires the parent to set an explicit height because h-full collapses inside a flex row otherwise.
Theme key: Separator. The default theme does not ship a labeled "OR" variant; compose two separators around a span when you need one.
Tabs
Tabs switches between sibling panels that occupy the same space. The root owns the selected value (default-value uncontrolled, v-model controlled), a TabsList holds the TabsTriggers, and one TabsContent per value renders the panel. Trigger and content pair up by matching value, not by order.
activation-mode decides what keyboard arrows do: the default automatic selects as focus moves, while manual moves focus and waits for Enter or Space. Prefer manual when selecting a tab does real work, such as issuing a request.
Tabs are for peer views of the same subject. They are not a wizard: nothing in the component enforces an order or a completion state, which is what Stepper is for.
Theme keys: Tabs, TabsList, TabsTrigger, TabsContent.
Drawer
Drawer is an edge-anchored overlay that slides in and can be dismissed by dragging it back. It is built on vaul, so it carries touch affordances a Sheet does not: a drag handle, a velocity-aware close threshold, and optional snap points. Reach for it on touch-first surfaces, and for Sheet on pointer-first ones; the two are otherwise the same shape of thing.
DrawerContent portals to the document body and renders its own overlay and handle. Composition mirrors the dialog family: a trigger, then a content region holding a header with a title and description, a body, and a footer.
Two defaults matter for embedding a drawer anywhere other than a full page. should-scale-background is on by default and scales the page behind the drawer, and vaul also writes styles onto document.body while open. Both are right in an application and wrong inside a documentation page, so the demo turns them off. An application usually leaves them alone.
The drawer needs real layout and pointer measurement, so it renders client-side only. Its markup follows the documented composition; unlike the rest of this page it is not covered by a unit test, because vaul cannot mount under jsdom.
Theme keys: DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, DrawerOverlay.
Resizable
ResizablePanelGroup splits a region into panels the reader can resize by dragging the handle between them. direction sets the axis. Each ResizablePanel takes sizes as percentages of the group, and a panel can be collapsible with a collapsed-size so a drag past its minimum snaps it shut instead of fighting the pointer.
Give the group an auto-save-id and it persists the layout to storage, so a reader's split survives a reload. The demo below omits it deliberately: a documentation page should not write layout state to your browser.
ResizableHandle is the drag target; with-handle adds a visible grip. Without it the handle is still there and still draggable, just a hairline.
Theme keys: ResizablePanelGroup, ResizableHandle.
AspectRatio
AspectRatio holds a box at a fixed width-to-height ratio while its width is free to change. Pass ratio as a number, so 16:9 is 16 / 9. The child fills the reserved box.
Use it to stop layout shift: the space is reserved before the content arrives, so a slow image or an embed does not push the page around as it loads.
Theme keys: AspectRatio.