Button
High Usage • Primary, secondary, outline, ghost variants with sizes and loading states.
Examples: Submit buttons, CTAs, navigation actions
A comprehensive collection of 40+ production-ready UI components built with shadcn-vue, Radix Vue, and TailwindCSS. Each component is designed with accessibility, performance, and developer experience in mind.
Complete set of UI components covering all common interface patterns and interactions
Built with Radix Vue primitives ensuring WCAG compliance and keyboard navigation
Consistent styling with CSS variables, semantic tokens, and automatic dark mode
Mobile-first design with responsive breakpoints and touch-friendly interactions
Easy theming with CSS variables and utility classes for rapid customization
Tree-shakable components with minimal bundle impact and optimized rendering
Essential form controls and input components for data collection.
Button
High Usage • Primary, secondary, outline, ghost variants with sizes and loading states.
Examples: Submit buttons, CTAs, navigation actions
Input
High Usage • Text inputs with validation states, icons, and helper text support.
Examples: Search fields, form inputs, filters
Select
High Usage • Dropdown selection with search, multi-select, and grouping options.
Examples: Country selection, category filters, option lists
Checkbox
Medium Usage • Single and group checkboxes with indeterminate states.
Examples: Terms acceptance, bulk selections, feature toggles
Radio Group
Medium Usage • Exclusive selection controls with custom styling options.
Examples: Payment methods, preferences, single choice questions
Switch
Medium Usage • Toggle controls for boolean settings and feature flags.
Examples: Dark mode toggle, notification settings, feature switches
Slider
Medium Usage • Range inputs for numeric values with step controls.
Examples: Price ranges, volume controls, rating inputs
Textarea
Medium Usage • Multi-line text inputs with resize controls and validation.
Examples: Comments, descriptions, feedback forms
Navigation and routing components for seamless user journeys.
Navigation Menu
High Usage • Multi-level navigation with dropdown support and active states.
Examples: Main navigation, sidebar menus, header navigation
Breadcrumb
High Usage • Hierarchical navigation with customizable separators and icons.
Examples: Page navigation, category paths, step indicators
Pagination
High Usage • Page navigation with customizable page sizes and jump controls.
Examples: Table pagination, search results, content lists
Tabs
High Usage • Content organization with horizontal and vertical orientations.
Examples: Settings panels, content categories, data views
Sidebar
High Usage • Collapsible navigation panel with nested menu support.
Examples: Admin dashboards, app navigation, filter panels
Menubar
Medium Usage • Application-level menu with keyboard shortcuts and submenus.
Examples: Desktop app menus, context menus, action bars
User feedback and notification components for better UX.
Alert
High Usage • Status messages with severity levels and dismissible options.
Examples: Success messages, error notifications, warning alerts
Toast (Sonner)
High Usage • Non-blocking notifications with actions and auto-dismiss.
Examples: Save confirmations, error messages, action feedback
Dialog
High Usage • Modal dialogs with backdrop, focus management, and keyboard handling.
Examples: Confirmations, forms, detailed views
Alert Dialog
Medium Usage • Confirmation dialogs for destructive or important actions.
Examples: Delete confirmations, logout prompts, irreversible actions
Sheet
Medium Usage • Slide-in panels from screen edges with overlay support.
Examples: Mobile menus, filter panels, detail views
Tooltip
Medium Usage • Contextual help text with positioning and delay controls.
Examples: Icon explanations, help text, additional information
Popover
Medium Usage • Floating content containers with positioning and interactions.
Examples: Action menus, help content, additional options
Hover Card
Low Usage • Rich hover previews with detailed content and media.
Examples: User profiles, link previews, detailed tooltips
Components for organizing and presenting information.
Table
High Usage • Data tables with sorting, filtering, and selection capabilities.
Examples: User lists, data grids, reports
Badge
High Usage • Status indicators and labels with color variants.
Examples: Status labels, tags, notifications counts
Avatar
High Usage • User profile images with fallbacks and status indicators.
Examples: User profiles, comment authors, team members
Card
High Usage • Content containers with headers, footers, and flexible layouts.
Examples: Dashboard widgets, product cards, content blocks
Separator
Medium Usage • Visual dividers for content sections and grouping.
Examples: Menu separators, content dividers, section breaks
Skeleton
Medium Usage • Loading placeholders matching content structure.
Examples: Loading states, content placeholders, progressive loading
Progress
Medium Usage • Progress indicators for tasks and loading states.
Examples: File uploads, form completion, loading progress
Accordion
Medium Usage • Collapsible content sections with smooth animations.
Examples: FAQ sections, settings panels, content organization
Structural components for page layout and content organization.
Aspect Ratio
Medium Usage • Responsive containers maintaining specific aspect ratios.
Examples: Video containers, image placeholders, responsive media
Scroll Area
Medium Usage • Custom scrollable containers with styled scrollbars.
Examples: Content areas, lists, code blocks
Carousel
Low Usage • Image and content sliders with navigation controls.
Examples: Image galleries, testimonials, feature showcases
Stepper
Low Usage • Step-by-step process indicators with completion states.
Examples: Multi-step forms, onboarding flows, progress tracking
Advanced components for specific use cases.
Calendar
Medium Usage • Date selection with range support and customizable views.
Examples: Date pickers, event scheduling, booking systems
Date Picker
High Usage • Integrated date selection with input and calendar components.
Examples: Form date fields, filtering, event creation
Dropdown Menu
High Usage • Context menus with actions, separators, and sub-menus.
Examples: Action menus, context menus, option lists
Form
High Usage • Form layout components with validation and error handling.
Examples: Registration forms, settings, data entry
Label
High Usage • Accessible form labels with required indicators.
Examples: Form labels, field descriptions, input associations
Tags Input
Medium Usage • Multi-value input with tag creation and deletion.
Examples: Skill tags, categories, keywords
Number Field
Medium Usage • Numeric inputs with increment/decrement controls.
Examples: Quantity selectors, numeric settings, counters
Pin Input
Low Usage • One-time password and PIN entry with auto-focus.
Examples: 2FA codes, PIN verification, security codes
Range Calendar
Low Usage • Date range selection with start and end date support.
Examples: Booking periods, date filters, reporting ranges
<template> <div class="space-x-2"> <!-- Primary Button --> <Button>Primary</Button>
<!-- Secondary Button --> <Button variant="secondary">Secondary</Button>
<!-- Outline Button --> <Button variant="outline">Outline</Button>
<!-- Ghost Button --> <Button variant="ghost">Ghost</Button>
<!-- Destructive Button --> <Button variant="destructive">Delete</Button>
<!-- Loading Button --> <Button :disabled="loading"> <Loader2 v-if="loading" class="mr-2 h-4 w-4 animate-spin" /> {{ loading ? 'Saving...' : 'Save' }} </Button> </div></template>
<script setup lang="ts">import { Button } from '@/components/ui/button'import { Loader2 } from 'lucide-vue-next'
const loading = ref(false)</script>
<template> <form @submit="onSubmit" class="space-y-6 max-w-md"> <!-- Input Field --> <FormItem> <FormLabel for="email">Email</FormLabel> <FormControl> <Input id="email" v-model="formData.email" type="email" placeholder="Enter your email" /> </FormControl> <FormDescription> We'll never share your email with anyone else. </FormDescription> <FormMessage /> </FormItem>
<!-- Select Field --> <FormItem> <FormLabel>Country</FormLabel> <Select v-model="formData.country"> <FormControl> <SelectTrigger> <SelectValue placeholder="Select a country" /> </SelectTrigger> </FormControl> <SelectContent> <SelectItem value="us">United States</SelectItem> <SelectItem value="ca">Canada</SelectItem> <SelectItem value="uk">United Kingdom</SelectItem> </SelectContent> </Select> <FormMessage /> </FormItem>
<!-- Checkbox --> <FormItem class="flex flex-row items-start space-x-3 space-y-0"> <FormControl> <Checkbox v-model:checked="formData.newsletter" /> </FormControl> <div class="space-y-1 leading-none"> <FormLabel>Subscribe to newsletter</FormLabel> <FormDescription> Receive updates about new features and releases. </FormDescription> </div> </FormItem>
<Button type="submit" class="w-full"> Create Account </Button> </form></template>
<script setup lang="ts">import { Button } from '@/components/ui/button'import { Input } from '@/components/ui/input'import { Checkbox } from '@/components/ui/checkbox'import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from '@/components/ui/select'import { FormControl, FormDescription, FormItem, FormLabel, FormMessage,} from '@/components/ui/form'
const formData = reactive({ email: '', country: '', newsletter: false})
const onSubmit = (event: Event) => { event.preventDefault() console.log('Form submitted:', formData)}</script>
<template> <div class="space-x-2"> <!-- Dialog Example --> <Dialog> <DialogTrigger as-child> <Button variant="outline">Open Dialog</Button> </DialogTrigger> <DialogContent class="sm:max-w-md"> <DialogHeader> <DialogTitle>Share link</DialogTitle> <DialogDescription> Anyone who has this link will be able to view this. </DialogDescription> </DialogHeader> <div class="flex items-center space-x-2"> <div class="grid flex-1 gap-2"> <Label for="link" class="sr-only">Link</Label> <Input id="link" :default-value="shareLink" readonly /> </div> <Button type="submit" size="sm" class="px-3"> <Copy class="h-4 w-4" /> </Button> </div> <DialogFooter class="sm:justify-start"> <DialogClose as-child> <Button type="button" variant="secondary"> Close </Button> </DialogClose> </DialogFooter> </DialogContent> </Dialog>
<!-- Sheet Example --> <Sheet> <SheetTrigger as-child> <Button variant="outline">Open Sheet</Button> </SheetTrigger> <SheetContent> <SheetHeader> <SheetTitle>Edit profile</SheetTitle> <SheetDescription> Make changes to your profile here. Click save when you're done. </SheetDescription> </SheetHeader> <div class="grid gap-4 py-4"> <div class="grid grid-cols-4 items-center gap-4"> <Label for="name" class="text-right">Name</Label> <Input id="name" value="Pedro Duarte" class="col-span-3" /> </div> <div class="grid grid-cols-4 items-center gap-4"> <Label for="username" class="text-right">Username</Label> <Input id="username" value="@peduarte" class="col-span-3" /> </div> </div> <SheetFooter> <SheetClose as-child> <Button type="submit">Save changes</Button> </SheetClose> </SheetFooter> </SheetContent> </Sheet> </div></template>
<script setup lang="ts">import { Button } from '@/components/ui/button'import { Input } from '@/components/ui/input'import { Label } from '@/components/ui/label'import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from '@/components/ui/dialog'import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger,} from '@/components/ui/sheet'import { Copy } from 'lucide-vue-next'
const shareLink = 'https://ui.shadcn.com/docs/installation'</script>
The UI library uses a sophisticated CSS variables system for consistent theming:
:root { /* Color Palette */ --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; --popover: 0 0% 100%; --popover-foreground: 222.2 84% 4.9%; --primary: 221.2 83.2% 53.3%; --primary-foreground: 210 40% 98%; --secondary: 210 40% 96%; --secondary-foreground: 222.2 84% 4.9%; --muted: 210 40% 96%; --muted-foreground: 215.4 16.3% 46.9%; --accent: 210 40% 96%; --accent-foreground: 222.2 84% 4.9%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; --ring: 221.2 83.2% 53.3%; --radius: 0.5rem;}
.dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; --card: 222.2 84% 4.9%; --card-foreground: 210 40% 98%; /* ... dark mode variables */}
<template> <div class="theme-provider"> <!-- Components automatically adapt to theme --> <Card> <CardHeader> <CardTitle>Theme-Aware Component</CardTitle> <CardDescription> This card automatically adapts to light/dark mode </CardDescription> </CardHeader> <CardContent> <Button>Primary Button</Button> <Button variant="secondary">Secondary Button</Button> </CardContent> </Card> </div></template>
<style>/* Theme detection happens automatically via CSS media queries */@media (prefers-color-scheme: dark) { :root { /* Dark mode CSS variables applied automatically */ }}</style>
<template> <div> <!-- Theme Toggle Button --> <Button @click="toggleTheme" variant="outline" size="icon"> <Sun v-if="isDark" class="h-[1.2rem] w-[1.2rem]" /> <Moon v-else class="h-[1.2rem] w-[1.2rem]" /> <span class="sr-only">Toggle theme</span> </Button>
<!-- Theme-aware content --> <div class="mt-4 space-y-4"> <Alert> <AlertTriangle class="h-4 w-4" /> <AlertTitle>Theme Status</AlertTitle> <AlertDescription> Current theme: {{ isDark ? 'Dark' : 'Light' }} mode </AlertDescription> </Alert> </div> </div></template>
<script setup lang="ts">import { Button } from '@/components/ui/button'import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'import { Sun, Moon, AlertTriangle } from 'lucide-vue-next'
const isDark = useDark()const toggleTheme = useToggle(isDark)</script>
/* Custom brand colors */:root { --primary: 142 76% 36%; /* Custom green */ --secondary: 210 40% 96%; /* Light gray */ --accent: 142 76% 90%; /* Light green */ --destructive: 0 84% 60%; /* Red for errors */
/* Chart colors for data visualization */ --color-chart-1: 142 76% 36%; --color-chart-2: 197 71% 73%; --color-chart-3: 43 74% 66%; --color-chart-4: 27 87% 67%; --color-chart-5: 15 86% 65%;}
.dark { --primary: 142 76% 40%; /* Brighter in dark mode */ --secondary: 217.2 32.6% 17.5%; --accent: 142 76% 15%; /* ... other dark mode adjustments */}
Most components follow consistent API patterns:
Prop | Type | Default | Description |
---|---|---|---|
as | string | Component | Component default | Change the component’s rendered element |
as-child | boolean | false | Merge props into immediate child |
class | string | - | Additional CSS classes |
variant | string | ’default’ | Visual variant of the component |
size | string | ’default’ | Size variant of the component |
// Button component variantstype ButtonVariant = | 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
type ButtonSize = | 'default' | 'sm' | 'lg' | 'icon'
// Usage examples<Button variant="outline" size="sm">Small Outline</Button><Button variant="destructive" size="lg">Large Destructive</Button><Button variant="ghost" size="icon"><Settings /></Button>
<script setup lang="ts">// ✅ Good: Import only needed componentsimport { Button } from '@/components/ui/button'import { Input } from '@/components/ui/input'import { Card, CardContent } from '@/components/ui/card'
// ❌ Avoid: Importing entire UI library// import * as UI from '@/components/ui'</script>
<template> <!-- ✅ Good: Proper ARIA labels and keyboard support --> <Dialog> <DialogTrigger as-child> <Button aria-describedby="dialog-description"> Open Settings </Button> </DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle>Settings</DialogTitle> <DialogDescription id="dialog-description"> Customize your application preferences </DialogDescription> </DialogHeader> <!-- Dialog content --> </DialogContent> </Dialog>
<!-- ✅ Good: Form labels and descriptions --> <FormItem> <FormLabel for="username">Username</FormLabel> <FormControl> <Input id="username" aria-describedby="username-description" v-model="username" /> </FormControl> <FormDescription id="username-description"> Choose a unique username for your account </FormDescription> </FormItem></template>
<template> <Card class="w-full max-w-md"> <CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2"> <CardTitle class="text-sm font-medium">Total Revenue</CardTitle> <DollarSign class="h-4 w-4 text-muted-foreground" /> </CardHeader> <CardContent> <div class="text-2xl font-bold">$45,231.89</div> <p class="text-xs text-muted-foreground"> +20.1% from last month </p> <div class="mt-4"> <Progress :value="65" class="w-full" /> </div> </CardContent> </Card></template>
<script setup lang="ts">import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'import { Progress } from '@/components/ui/progress'import { DollarSign } from 'lucide-vue-next'</script>
<template> <form @submit="handleSubmit" class="space-y-8 max-w-2xl"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> <!-- Personal Information --> <div class="space-y-4"> <h3 class="text-lg font-semibold">Personal Information</h3>
<FormItem> <FormLabel for="firstName">First Name</FormLabel> <FormControl> <Input id="firstName" v-model="form.firstName" /> </FormControl> <FormMessage>{{ errors.firstName }}</FormMessage> </FormItem>
<FormItem> <FormLabel for="lastName">Last Name</FormLabel> <FormControl> <Input id="lastName" v-model="form.lastName" /> </FormControl> <FormMessage>{{ errors.lastName }}</FormMessage> </FormItem>
<FormItem> <FormLabel>Country</FormLabel> <Select v-model="form.country"> <FormControl> <SelectTrigger> <SelectValue placeholder="Select your country" /> </SelectTrigger> </FormControl> <SelectContent> <SelectItem value="us">United States</SelectItem> <SelectItem value="ca">Canada</SelectItem> <SelectItem value="uk">United Kingdom</SelectItem> </SelectContent> </Select> <FormMessage>{{ errors.country }}</FormMessage> </FormItem> </div>
<!-- Preferences --> <div class="space-y-4"> <h3 class="text-lg font-semibold">Preferences</h3>
<FormItem class="flex flex-row items-start space-x-3 space-y-0"> <FormControl> <Checkbox v-model:checked="form.newsletter" /> </FormControl> <div class="space-y-1 leading-none"> <FormLabel>Email notifications</FormLabel> <FormDescription> Receive email updates about your account </FormDescription> </div> </FormItem>
<FormItem class="flex flex-row items-center justify-between rounded-lg border p-4"> <div class="space-y-0.5"> <FormLabel class="text-base">Dark Mode</FormLabel> <FormDescription> Enable dark mode for the interface </FormDescription> </div> <FormControl> <Switch v-model:checked="form.darkMode" /> </FormControl> </FormItem> </div> </div>
<div class="flex justify-end space-x-2"> <Button type="button" variant="outline" @click="handleCancel"> Cancel </Button> <Button type="submit" :disabled="isSubmitting"> <Loader2 v-if="isSubmitting" class="mr-2 h-4 w-4 animate-spin" /> {{ isSubmitting ? 'Saving...' : 'Save Changes' }} </Button> </div> </form></template>