{"schemaVersion":1,"library":"shadcn/ui","base":"Radix","theme":"Postede","instructions":"/design/agents","markdown":"/design/llms.txt","rules":["Read AGENTS.md, docs/lab-design-system.md and the relevant /design component page before changing UI. Installed source in src/components/ui is the API authority.","Use source-owned shadcn/Radix components from @/components/ui. Prefer named variants and semantic theme tokens; preserve Postede’s existing palette, typography, spacing and responsive behavior.","Compose Field, FieldLabel, a control, FieldDescription and FieldError for forms. Preserve names, values, validation, refs, submit types and pending/error behavior.","Give dialogs a title, avatars a fallback and icon buttons an accessible name. Keep SelectItem in SelectGroup, menu items in DropdownMenuGroup and TabsTrigger in TabsList.","Use the shared toast API from @/features/ui/toast. It preserves async actions, retries and native-modal placement through Sonner.","Use NativeDialog, NativeCheckbox, NativeSwitch and inline surface adapters only when preserving an existing host contract. Prefer declarative primitives for new independent controls; do not spread unstyled as a default.","Keep Tiptap, drag-and-drop scheduling, charts, provider previews and Posty as product compositions. Reuse their shared controls rather than replacing their interaction engines.","Add missing official components with the shadcn CLI, inspect generated files, and preserve local adaptations. Never reinitialize or overwrite customized primitives to add a component.","Update the component reference, example and agent reference together when a primitive API changes. Run npm run ui:audit, appropriate checks, and real-browser desktop/mobile verification; distinguish tested behavior from inference."],"workflow":[["Inspect","Find the matching component and read its installed source. Check existing product usage before creating a new abstraction."],["Compose","Start from the documented import and example. Use named variants, semantic tokens and the correct compound structure."],["Preserve","Keep form, focus, state, cancellation and responsive contracts intact. Use compatibility adapters only where those contracts require them."],["Verify","Exercise keyboard, Escape, disabled/pending/error states and narrow layouts. Run the UI audit and relevant tests, then update the reference."]],"tokens":[["Canvas","--background","Page background"],["Text","--foreground","Primary text"],["Surface","--card","Panels and cards"],["Elevated","--popover","Menus and overlays"],["Field","--field","Text controls"],["Muted","--muted-foreground","Supporting text"],["Primary","--primary","Primary actions"],["Selected","--accent-commit","Active and selected states"],["Danger","--destructive","Destructive actions and errors"],["Border","--border","Quiet boundaries"]],"components":[{"name":"Button","slug":"button","group":"actions","usage":"Run an action or submit a form.","api":"variant: default, outline, secondary, ghost, destructive, link; size: xs, sm, default, lg, icon variants; asChild; disabled","accessibility":"Use an accessible name for icon actions. Keep a visible focus indicator.","source":"src/components/ui/button.tsx","importPath":"@/components/ui/button","href":"/design/button","example":"import { Button } from \"@/components/ui/button\";\n\n<>\n<Button type=\"submit\">Save changes</Button>\n<Button type=\"button\" variant=\"outline\">Cancel</Button>\n</>"},{"name":"ButtonGroup","slug":"button-group","group":"actions","usage":"Join related actions without changing their individual behavior.","api":"orientation: horizontal or vertical; standard div attributes","accessibility":"Use distinct action labels; do not use a button group as a radio group.","source":"src/components/ui/button-group.tsx","importPath":"@/components/ui/button-group","href":"/design/button-group","example":"import { ButtonGroup } from \"@/components/ui/button-group\";\nimport { Button } from \"@/components/ui/button\";\n\n<>\n<ButtonGroup aria-label=\"Record actions\">\n  <Button variant=\"outline\">Edit</Button>\n  <Button variant=\"outline\">Duplicate</Button>\n</ButtonGroup>\n</>"},{"name":"Input","slug":"input","group":"fields","usage":"Enter one line, search, or retain a browser-native input contract.","api":"Native input props: type, name, value/defaultValue, required, disabled, ref; unstyled composition","accessibility":"Keep labels associated and names stable. File, hidden, color, date/time and native radio types retain browser behavior.","source":"src/components/ui/input.tsx","importPath":"@/components/ui/input","href":"/design/input","example":"import { Field, FieldLabel } from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\n\n<>\n<Field>\n  <FieldLabel htmlFor=\"project-name\">Project name</FieldLabel>\n  <Input id=\"project-name\" name=\"projectName\" required />\n</Field>\n</>"},{"name":"Textarea","slug":"textarea","group":"fields","usage":"Enter plain multiline content.","api":"Native textarea props, rows, value/defaultValue, required, ref","accessibility":"Do not replace the rich-text composer with Textarea. Keep an error description associated.","source":"src/components/ui/textarea.tsx","importPath":"@/components/ui/textarea","href":"/design/textarea","example":"import { Field, FieldLabel } from \"@/components/ui/field\";\nimport { Textarea } from \"@/components/ui/textarea\";\n\n<>\n<Field>\n  <FieldLabel htmlFor=\"notes\">Notes</FieldLabel>\n  <Textarea id=\"notes\" name=\"notes\" rows={4} />\n</Field>\n</>"},{"name":"Field","slug":"field","group":"fields","usage":"Group a label, control, description and error.","api":"FieldGroup, FieldSet, FieldLegend, FieldLabel, FieldDescription, FieldError; data-invalid; data-disabled","accessibility":"Set aria-invalid on the control and connect help/error IDs with aria-describedby.","source":"src/components/ui/field.tsx","importPath":"@/components/ui/field","href":"/design/field","example":"import { Field, FieldLabel, FieldError } from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\n\n<>\n<Field data-invalid={invalid}>\n  <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n  <Input id=\"email\" type=\"email\" aria-invalid={invalid} aria-describedby=\"email-error\" />\n  <FieldError id=\"email-error\">{invalid ? \"Enter a valid email.\" : null}</FieldError>\n</Field>\n</>"},{"name":"Label","slug":"label","group":"fields","usage":"Name a control, including an existing composed field.","api":"htmlFor, asChild, native label attributes; unstyled composition","accessibility":"A placeholder alone is not a label. Prefer FieldLabel inside Field.","source":"src/components/ui/label.tsx","importPath":"@/components/ui/label","href":"/design/label","example":"import { Label } from \"@/components/ui/label\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\n\n<>\n<Label htmlFor=\"reviewed\">Reviewed</Label>\n<Checkbox id=\"reviewed\" />\n</>"},{"name":"Select","slug":"select","group":"fields","usage":"Choose one option from a list.","api":"value/defaultValue, onValueChange, name, required, disabled; SelectTrigger, SelectValue, SelectContent, SelectGroup, SelectItem","accessibility":"Keep items inside a group. Use the dialog portal container for a select inside a native modal.","source":"src/components/ui/select.tsx","importPath":"@/components/ui/select","href":"/design/select","example":"import { Select, SelectTrigger, SelectValue, SelectContent, SelectGroup, SelectItem } from \"@/components/ui/select\";\n\n<>\n<Select defaultValue=\"draft\">\n  <SelectTrigger aria-label=\"Status\"><SelectValue /></SelectTrigger>\n  <SelectContent><SelectGroup>\n    <SelectItem value=\"draft\">Draft</SelectItem>\n    <SelectItem value=\"approved\">Approved</SelectItem>\n  </SelectGroup></SelectContent>\n</Select>\n</>"},{"name":"Checkbox","slug":"checkbox","group":"selection","usage":"Toggle an independent selection.","api":"checked/defaultChecked, onCheckedChange, disabled, name; checked may be indeterminate","accessibility":"Use NativeCheckbox composition when checked CSS selectors, native change events, form behavior or input refs must be retained.","source":"src/components/ui/checkbox.tsx","importPath":"@/components/ui/checkbox","href":"/design/checkbox","example":"import { Checkbox } from \"@/components/ui/checkbox\";\nimport { Label } from \"@/components/ui/label\";\n\n<>\n<div className=\"flex items-center gap-2\">\n  <Checkbox id=\"reviewed\" name=\"reviewed\" />\n  <Label htmlFor=\"reviewed\">Reviewed</Label>\n</div>\n</>"},{"name":"Switch","slug":"switch","group":"selection","usage":"Enable or disable an immediate setting.","api":"checked/defaultChecked, onCheckedChange, disabled; size: sm or default","accessibility":"Use a stable setting label. NativeSwitch preserves existing checkbox-input event and form contracts.","source":"src/components/ui/switch.tsx","importPath":"@/components/ui/switch","href":"/design/switch","example":"import { Switch } from \"@/components/ui/switch\";\nimport { Label } from \"@/components/ui/label\";\n\n<>\n<div className=\"flex items-center gap-2\">\n  <Switch id=\"notifications\" checked={enabled} onCheckedChange={setEnabled} />\n  <Label htmlFor=\"notifications\">Notifications</Label>\n</div>\n</>"},{"name":"RadioGroup","slug":"radio-group","group":"selection","usage":"Select exactly one option.","api":"value/defaultValue, onValueChange, orientation; RadioGroupItem value","accessibility":"Group with a meaningful accessible name. Existing native radio fields use Input to retain browser name grouping and events.","source":"src/components/ui/radio-group.tsx","importPath":"@/components/ui/radio-group","href":"/design/radio-group","example":"import { RadioGroup, RadioGroupItem } from \"@/components/ui/radio-group\";\nimport { Label } from \"@/components/ui/label\";\n\n<>\n<RadioGroup defaultValue=\"daily\" aria-label=\"Frequency\">\n  <RadioGroupItem value=\"daily\" id=\"daily\" /><Label htmlFor=\"daily\">Daily</Label>\n  <RadioGroupItem value=\"weekly\" id=\"weekly\" /><Label htmlFor=\"weekly\">Weekly</Label>\n</RadioGroup>\n</>"},{"name":"Slider","slug":"slider","group":"selection","usage":"Adjust a bounded numeric value.","api":"value/defaultValue arrays, onValueChange, min, max, step, disabled","accessibility":"Name every thumb and show the current value as text.","source":"src/components/ui/slider.tsx","importPath":"@/components/ui/slider","href":"/design/slider","example":"import { Slider } from \"@/components/ui/slider\";\n\n<>\n<Slider aria-label=\"Volume\" defaultValue={[50]} min={0} max={100} step={1} />\n</>"},{"name":"ToggleGroup","slug":"toggle-group","group":"selection","usage":"Switch between a small set of peer options.","api":"type: single or multiple; value/defaultValue; onValueChange; ToggleGroupItem","accessibility":"Use an accessible group label. Use Tabs when options reveal associated panels.","source":"src/components/ui/toggle-group.tsx","importPath":"@/components/ui/toggle-group","href":"/design/toggle-group","example":"import { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\n\n<>\n<ToggleGroup type=\"single\" defaultValue=\"list\" aria-label=\"View\">\n  <ToggleGroupItem value=\"list\">List</ToggleGroupItem>\n  <ToggleGroupItem value=\"grid\">Grid</ToggleGroupItem>\n</ToggleGroup>\n</>"},{"name":"Toggle","slug":"toggle","group":"selection","usage":"Press or release a single optional mode.","api":"pressed/defaultPressed, onPressedChange, disabled; variant and size","accessibility":"Expose the mode name independently of its pressed state.","source":"src/components/ui/toggle.tsx","importPath":"@/components/ui/toggle","href":"/design/toggle","example":"import { Toggle } from \"@/components/ui/toggle\";\n\n<>\n<Toggle aria-label=\"Bold\">Bold</Toggle>\n</>"},{"name":"Tabs","slug":"tabs","group":"navigation","usage":"Switch related views in the same context.","api":"value/defaultValue, onValueChange, activationMode, orientation; TabsList, TabsTrigger, TabsContent","accessibility":"Keep trigger values and panel IDs linked. Existing hosts retain their selection and keyboard handlers through asChild composition.","source":"src/components/ui/tabs.tsx","importPath":"@/components/ui/tabs","href":"/design/tabs","example":"import { Tabs, TabsList, TabsTrigger, TabsContent } from \"@/components/ui/tabs\";\n\n<>\n<Tabs defaultValue=\"preview\">\n  <TabsList><TabsTrigger value=\"preview\">Preview</TabsTrigger><TabsTrigger value=\"details\">Details</TabsTrigger></TabsList>\n  <TabsContent value=\"preview\">Preview content</TabsContent>\n  <TabsContent value=\"details\">Details content</TabsContent>\n</Tabs>\n</>"},{"name":"Accordion","slug":"accordion","group":"navigation","usage":"Expand related sections independently or one at a time.","api":"type: single or multiple; collapsible; value/defaultValue; AccordionItem, AccordionTrigger, AccordionContent","accessibility":"Use a real heading hierarchy and concise trigger labels.","source":"src/components/ui/accordion.tsx","importPath":"@/components/ui/accordion","href":"/design/accordion","example":"import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from \"@/components/ui/accordion\";\n\n<>\n<Accordion type=\"single\" collapsible>\n  <AccordionItem value=\"details\"><AccordionTrigger>Details</AccordionTrigger>\n    <AccordionContent>Supporting context.</AccordionContent>\n  </AccordionItem>\n</Accordion>\n</>"},{"name":"Collapsible","slug":"collapsible","group":"navigation","usage":"Show optional detail within the current task.","api":"open/defaultOpen, onOpenChange; CollapsibleTrigger, CollapsibleContent","accessibility":"Disclosure composes native details/summary so open selectors, default opening, SSR and retained form state keep working.","source":"src/components/ui/collapsible.tsx","importPath":"@/components/ui/collapsible","href":"/design/collapsible","example":"import { Collapsible, CollapsibleTrigger, CollapsibleContent } from \"@/components/ui/collapsible\";\nimport { Button } from \"@/components/ui/button\";\n\n<>\n<Collapsible>\n  <CollapsibleTrigger asChild><Button variant=\"outline\">More details</Button></CollapsibleTrigger>\n  <CollapsibleContent>Supporting context.</CollapsibleContent>\n</Collapsible>\n</>"},{"name":"Dialog","slug":"dialog","group":"overlays","usage":"Complete a focused task in an overlay.","api":"open/defaultOpen, onOpenChange; DialogTrigger, DialogContent, DialogTitle, DialogDescription, DialogClose","accessibility":"Always name the dialog, manage focus deliberately, and test Escape and nested overlays.","source":"src/components/ui/dialog.tsx","importPath":"@/components/ui/dialog","href":"/design/dialog","example":"import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogClose } from \"@/components/ui/dialog\";\nimport { Button } from \"@/components/ui/button\";\n\n<>\n<Dialog>\n  <DialogTrigger asChild><Button variant=\"outline\">Edit</Button></DialogTrigger>\n  <DialogContent>\n    <DialogHeader><DialogTitle>Edit details</DialogTitle><DialogDescription>Update this record.</DialogDescription></DialogHeader>\n    <DialogClose asChild><Button variant=\"outline\">Cancel</Button></DialogClose>\n  </DialogContent>\n</Dialog>\n</>"},{"name":"NativeDialog","slug":"native-dialog","group":"overlays","usage":"Keep an existing native top-layer dialog and imperative ref.","api":"Native dialog props, ref.showModal(), ref.close(), onCancel, onClose","accessibility":"Composes DialogContent asChild. Native cancellation guards and backdrop styling remain authoritative; do not substitute it for every new dialog.","source":"src/components/ui/native-dialog.tsx","importPath":"@/components/ui/native-dialog","href":"/design/native-dialog","example":"import { NativeDialog } from \"@/components/ui/native-dialog\";\nimport { Button } from \"@/components/ui/button\";\n\n<>\n<NativeDialog ref={dialogRef} aria-label=\"Edit details\" onCancel={guardUnsavedChanges}>\n  <h2>Edit details</h2>\n  <Button onClick={() => dialogRef.current?.close()}>Close</Button>\n</NativeDialog>\n{/* Existing host opens with dialogRef.current?.showModal(). */}\n</>"},{"name":"DialogSurface","slug":"dialog-surface","group":"overlays","usage":"Compose an existing host-managed product overlay.","api":"One child carrying its existing ref, ARIA name, classes and event handlers","accessibility":"The host owns mounting, focus trapping and dismissal. Use declarative Dialog for an independent new modal.","source":"src/components/ui/dialog-surface.tsx","importPath":"@/components/ui/dialog-surface","href":"/design/dialog-surface","example":"import { DialogSurface } from \"@/components/ui/dialog-surface\";\n\n<>\n{open ? (\n  <DialogSurface>\n    <div role=\"dialog\" aria-label=\"Existing task\" ref={hostRef}>\n      {/* Existing focus management, layout and dismissal stay with the host. */}\n    </div>\n  </DialogSurface>\n) : null}\n</>"},{"name":"AlertDialog","slug":"alert-dialog","group":"overlays","usage":"Confirm a consequential action with an explicit decision.","api":"open/onOpenChange; AlertDialogTitle, Description, Cancel, Action","accessibility":"Name the consequence and count affected objects. Never perform a destructive action in a demo.","source":"src/components/ui/alert-dialog.tsx","importPath":"@/components/ui/alert-dialog","href":"/design/alert-dialog","example":"import { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from \"@/components/ui/alert-dialog\";\nimport { Button } from \"@/components/ui/button\";\n\n<>\n<AlertDialog>\n  <AlertDialogTrigger asChild><Button variant=\"destructive\">Delete</Button></AlertDialogTrigger>\n  <AlertDialogContent>\n    <AlertDialogHeader><AlertDialogTitle>Delete this record?</AlertDialogTitle><AlertDialogDescription>This cannot be undone.</AlertDialogDescription></AlertDialogHeader>\n    <AlertDialogFooter><AlertDialogCancel>Cancel</AlertDialogCancel><AlertDialogAction onClick={handleConfirmedDelete}>Delete record</AlertDialogAction></AlertDialogFooter>\n  </AlertDialogContent>\n</AlertDialog>\n</>"},{"name":"DropdownMenu","slug":"dropdown-menu","group":"overlays","usage":"Offer commands attached to a trigger.","api":"DropdownMenuTrigger, Content, Group, Item, CheckboxItem, RadioGroup, RadioItem, Separator","accessibility":"Group items. Use onSelect for commands and disabled for unavailable commands. MenuSurface preserves an existing host's inline positioning and dismissal.","source":"src/components/ui/dropdown-menu.tsx","importPath":"@/components/ui/dropdown-menu","href":"/design/dropdown-menu","example":"import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\nimport { Button } from \"@/components/ui/button\";\n\n<>\n<DropdownMenu>\n  <DropdownMenuTrigger asChild><Button variant=\"outline\">Actions</Button></DropdownMenuTrigger>\n  <DropdownMenuContent><DropdownMenuGroup>\n    <DropdownMenuItem onSelect={handleEdit}>Edit</DropdownMenuItem>\n  </DropdownMenuGroup></DropdownMenuContent>\n</DropdownMenu>\n</>"},{"name":"Popover","slug":"popover","group":"overlays","usage":"Show a small interactive tool near its trigger.","api":"open/defaultOpen, onOpenChange; PopoverTrigger, PopoverContent, PopoverAnchor","accessibility":"Provide a clear accessible name. Use Dialog when the task requires a modal workflow.","source":"src/components/ui/popover.tsx","importPath":"@/components/ui/popover","href":"/design/popover","example":"import { Popover, PopoverTrigger, PopoverContent } from \"@/components/ui/popover\";\nimport { Button } from \"@/components/ui/button\";\n\n<>\n<Popover>\n  <PopoverTrigger asChild><Button variant=\"outline\">Options</Button></PopoverTrigger>\n  <PopoverContent aria-label=\"Options\">Contextual controls</PopoverContent>\n</Popover>\n</>"},{"name":"Tooltip","slug":"tooltip","group":"overlays","usage":"Explain a concise label or keyboard shortcut.","api":"TooltipProvider delayDuration; TooltipTrigger asChild; TooltipContent side, sideOffset","accessibility":"FastTooltip retains zero-delay product hints and delayed shortcut hints. TooltipSurface supports host-positioned chart and shortcut hints. Essential help belongs in visible text.","source":"src/components/ui/tooltip.tsx","importPath":"@/components/ui/tooltip","href":"/design/tooltip","example":"import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from \"@/components/ui/tooltip\";\nimport { Button } from \"@/components/ui/button\";\n\n<>\n<TooltipProvider><Tooltip>\n  <TooltipTrigger asChild><Button variant=\"outline\">Search</Button></TooltipTrigger>\n  <TooltipContent>Search your workspace</TooltipContent>\n</Tooltip></TooltipProvider>\n</>"},{"name":"Card","slug":"card","group":"data","usage":"Frame one surface or one repeated object.","api":"CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardAction; size; asChild","accessibility":"DashboardCard and LabPanel compose Card with existing 8px surfaces. Avoid decorative nested cards.","source":"src/components/ui/card.tsx","importPath":"@/components/ui/card","href":"/design/card","example":"import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from \"@/components/ui/card\";\nimport { Button } from \"@/components/ui/button\";\n\n<>\n<Card>\n  <CardHeader><CardTitle>Upcoming posts</CardTitle><CardDescription>This week</CardDescription></CardHeader>\n  <CardContent>Post list</CardContent>\n  <CardFooter><Button variant=\"outline\">View schedule</Button></CardFooter>\n</Card>\n</>"},{"name":"Table","slug":"table","group":"data","usage":"Compare structured records.","api":"TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption","accessibility":"Keep header semantics and readable type. The unstyled variant retains existing scroll containers, column widths and row behavior.","source":"src/components/ui/table.tsx","importPath":"@/components/ui/table","href":"/design/table","example":"import { Table, TableCaption, TableHeader, TableRow, TableHead, TableBody, TableCell } from \"@/components/ui/table\";\n\n<>\n<Table>\n  <TableCaption>Recent drafts</TableCaption>\n  <TableHeader><TableRow><TableHead>Title</TableHead><TableHead>Status</TableHead></TableRow></TableHeader>\n  <TableBody><TableRow><TableCell>Launch update</TableCell><TableCell>Draft</TableCell></TableRow></TableBody>\n</Table>\n</>"},{"name":"Avatar","slug":"avatar","group":"data","usage":"Identify a person or workspace in a stable frame.","api":"AvatarImage src/alt, AvatarFallback; size","accessibility":"Always provide a fallback. Channel logos and provider-specific previews remain product compositions.","source":"src/components/ui/avatar.tsx","importPath":"@/components/ui/avatar","href":"/design/avatar","example":"import { Avatar, AvatarImage, AvatarFallback } from \"@/components/ui/avatar\";\n\n<>\n<Avatar>\n  <AvatarImage src=\"/profile.jpg\" alt=\"Profile\" />\n  <AvatarFallback>JP</AvatarFallback>\n</Avatar>\n</>"},{"name":"Badge","slug":"badge","group":"data","usage":"Emphasize status or a short category.","api":"variant: default, secondary, destructive, outline, ghost, link; asChild","accessibility":"Use words as well as color. Ordinary status can remain plain text; avoid making every value a badge.","source":"src/components/ui/badge.tsx","importPath":"@/components/ui/badge","href":"/design/badge","example":"import { Badge } from \"@/components/ui/badge\";\n\n<>\n<Badge variant=\"secondary\">Draft</Badge>\n</>"},{"name":"Separator","slug":"separator","group":"data","usage":"Separate related groups without implying a new card.","api":"orientation: horizontal or vertical; decorative; asChild","accessibility":"Use decorative separators for visual spacing and a semantic separator only when the boundary conveys meaning.","source":"src/components/ui/separator.tsx","importPath":"@/components/ui/separator","href":"/design/separator","example":"import { Separator } from \"@/components/ui/separator\";\n\n<>\n<Separator decorative />\n</>"},{"name":"Alert","slug":"alert","group":"feedback","usage":"Explain a persistent condition beside the affected task.","api":"AlertTitle, AlertDescription; variant: default or destructive","accessibility":"Describe a useful recovery action. Do not replace field-level errors with a distant alert.","source":"src/components/ui/alert.tsx","importPath":"@/components/ui/alert","href":"/design/alert","example":"import { Alert, AlertTitle, AlertDescription } from \"@/components/ui/alert\";\n\n<>\n<Alert>\n  <AlertTitle>Changes saved</AlertTitle>\n  <AlertDescription>Your draft is ready to review.</AlertDescription>\n</Alert>\n</>"},{"name":"Sonner","slug":"sonner","group":"feedback","usage":"Acknowledge completed work or report an actionable failure.","api":"toast.success/error/info/show/dismiss; title, description, duration, optional async action","accessibility":"The shared toast store feeds Sonner while retaining action pending/retry, hover/focus pause, and native-modal placement.","source":"src/components/ui/sonner.tsx","importPath":"@/features/ui/toast","href":"/design/sonner","example":"import { toast } from \"@/features/ui/toast\";\n\ntoast.success(\"Changes saved\");\n// Keep the shared renderer mounted once at the application root."},{"name":"Progress","slug":"progress","group":"feedback","usage":"Show measurable progress.","api":"value, max, aria-label; ProgressSurface for existing host-rendered meters","accessibility":"Expose a valid numeric value and maximum. Keep indeterminate work separate from measurable completion.","source":"src/components/ui/progress.tsx","importPath":"@/components/ui/progress","href":"/design/progress","example":"import { Progress } from \"@/components/ui/progress\";\n\n<>\n<Progress value={60} max={100} aria-label=\"Upload progress\" />\n</>"},{"name":"Skeleton","slug":"skeleton","group":"feedback","usage":"Reserve the shape of content while it loads.","api":"className for dimensions; asChild for an existing loading layout","accessibility":"Preserve layout and respect reduced motion. Do not announce every decorative skeleton.","source":"src/components/ui/skeleton.tsx","importPath":"@/components/ui/skeleton","href":"/design/skeleton","example":"import { Skeleton } from \"@/components/ui/skeleton\";\n\n<>\n<div aria-hidden=\"true\" className=\"flex flex-col gap-3\">\n  <Skeleton className=\"h-4 w-3/4\" />\n  <Skeleton className=\"h-4 w-1/2\" />\n</div>\n</>"},{"name":"Spinner","slug":"spinner","group":"feedback","usage":"Indicate a short pending operation.","api":"Standard SVG props; data-icon when composed in a button","accessibility":"Pair with useful pending text. Use one meaningful live announcement for the operation.","source":"src/components/ui/spinner.tsx","importPath":"@/components/ui/spinner","href":"/design/spinner","example":"import { Button } from \"@/components/ui/button\";\nimport { Spinner } from \"@/components/ui/spinner\";\n\n<>\n<Button disabled aria-busy=\"true\">\n  <Spinner data-icon=\"inline-start\" />Saving…\n</Button>\n</>"},{"name":"Empty","slug":"empty","group":"feedback","usage":"Explain an empty result and its next useful action.","api":"EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent","accessibility":"Distinguish no data from no filter matches. Keep one obvious next action.","source":"src/components/ui/empty.tsx","importPath":"@/components/ui/empty","href":"/design/empty","example":"import { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent } from \"@/components/ui/empty\";\nimport { Button } from \"@/components/ui/button\";\n\n<>\n<Empty>\n  <EmptyHeader><EmptyTitle>No drafts yet</EmptyTitle><EmptyDescription>Create your first draft.</EmptyDescription></EmptyHeader>\n  <EmptyContent><Button>Create draft</Button></EmptyContent>\n</Empty>\n</>"}]}