Inputs & Forms
17 components for capturing user input. All support v-model, all flip with theme, all share the size scale (xs / sm / md / lg where applicable).
Button
interface Props {
icon?: string
trailingIcon?: string
variant?: 'solid' | 'soft' | 'ghost' | 'outline' | 'link' // 'solid'
color?: 'primary' | 'neutral' | 'warning' | 'info' | 'success' | 'error' // 'primary'
size?: '2xs' | 'xs' | 'sm' | 'md' | 'lg' // 'md'
loading?: boolean
disabled?: boolean
block?: boolean // full-width
to?: string // makes it an <RouterLink>
label?: string
}Slots: default, leading, trailing.
<Button icon="i-lucide-upload" @click="upload">Upload</Button>
<Button variant="ghost" color="error" trailing-icon="i-lucide-trash">Delete</Button>
<Button :loading="saving">Save</Button>Input
interface Props {
modelValue?: string | number
type?: string // 'text'
placeholder?: string
size?: 'xs' | 'sm' | 'md' | 'lg' // 'md'
disabled?: boolean
required?: boolean
icon?: string // leading icon
trailingIcon?: string
prefix?: string // e.g. 'W' for width
suffix?: string // e.g. '%'
variant?: 'outline' | 'soft' | 'none' // 'outline'
autofocus?: boolean
}<Input v-model="name" placeholder="Folder name" />
<Input v-model="width" type="number" prefix="W" suffix="px" />
<Input v-model="search" icon="i-lucide-search" placeholder="Search…" />Textarea
interface Props {
modelValue?: string
placeholder?: string
rows?: number // 3
size?: 'xs' | 'sm' | 'md' | 'lg'
disabled?: boolean
autoresize?: boolean // grows with content
variant?: 'outline' | 'none'
}<Textarea v-model="description" :rows="5" autoresize />Checkbox
interface Props {
modelValue?: boolean
label?: string
disabled?: boolean
color?: string
}<Checkbox v-model="agreed" label="I agree to the terms" />Switch
interface Props {
modelValue?: boolean
label?: string
disabled?: boolean
size?: 'xs' | 'sm' | 'md'
}<Switch v-model="dark" label="Dark mode" />RadioGroup
interface RadioOption {
value: string
label: string
description?: string
icon?: string
disabled?: boolean
}
interface Props {
modelValue?: string
options?: RadioOption[]
orientation?: 'vertical' | 'horizontal' // 'vertical'
size?: 'sm' | 'md' | 'lg'
variant?: 'default' | 'card'
disabled?: boolean
}<RadioGroup
v-model="plan"
variant="card"
:options="[
{ value: 'free', label: 'Free', description: '5 spaces' },
{ value: 'pro', label: 'Pro', description: 'Unlimited' },
]"
/>Slider
interface Props {
modelValue?: number // 0
min?: number // 0
max?: number // 100
step?: number // 1
disabled?: boolean
size?: 'xs' | 'sm' | 'md'
}<Slider v-model="volume" :max="100" />Select
Native <select> styled to match the rest of the kit. Fast to render, no JS popup.
interface SelectOption { label: string; value: string | number | null; disabled?: boolean }
interface Props {
modelValue?: string | number | null | (string | number | null)[]
options?: SelectOption[] | string[]
placeholder?: string // 'Select...'
size?: 'xs' | 'sm' | 'md' | 'lg'
disabled?: boolean
multiple?: boolean
icon?: string
variant?: 'outline' | 'soft' | 'none'
valueAttribute?: string // 'value'
optionAttribute?: string // 'label'
}<Select v-model="folder" :options="folders" placeholder="Choose folder" />SelectMenu
Custom popup version of Select with optional search and rich rows (icon + description).
interface Props extends SelectProps {
searchable?: boolean
}<SelectMenu
v-model="member"
searchable
:options="members.map(m => ({ value: m.id, label: m.name, icon: m.avatar }))"
/>MultiSelect
Pick multiple values. Renders selections as chips inside the trigger.
interface Props {
modelValue?: string[]
options?: { label: string; value: string; disabled?: boolean }[] | string[]
placeholder?: string
size?: 'xs' | 'sm' | 'md' | 'lg'
disabled?: boolean
searchable?: boolean // true
max?: number // hard cap on selections
}<MultiSelect v-model="tags" :options="allTags" :max="5" />Autocomplete
Input + dropdown of matching suggestions.
interface Props {
modelValue?: string | number | null
options?: { label: string; value: string | number; disabled?: boolean }[] | string[]
placeholder?: string
size?: 'xs' | 'sm' | 'md' | 'lg'
disabled?: boolean
icon?: string
emptyText?: string // 'No results'
clearable?: boolean
}<Autocomplete v-model="cityId" :options="cities" placeholder="Search cities…" clearable />ColorPicker
Self-contained HSV/HEX picker with optional alpha and preset swatches. No external color libs.
interface Props {
modelValue?: string // '#FF0000'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
alpha?: boolean
presets?: string[]
}Emits update:modelValue continuously and change on commit.
<ColorPicker v-model="brand" alpha :presets="['#4f8af5', '#ff6b6b', '#10b981']" />DatePicker
Input with calendar dropdown. Accepts free-form typing (ISO, slash, dash, pure-digit formats — 19820607 etc.); 2-digit years pivot at currentYear + 10.
interface Props {
modelValue?: string | null // ISO 'YYYY-MM-DD'
placeholder?: string // 'MM/DD/YYYY'
size?: 'xs' | 'sm' | 'md' | 'lg'
disabled?: boolean
min?: string // ISO bound
max?: string // ISO bound
format?: 'iso' | 'locale'
typeable?: boolean // true — allow free-form text entry
}<DatePicker v-model="due" min="2026-01-01" />Calendar
Full month/week/day view with events. Apple Calendar style.
interface CalendarEvent {
id: string | number
title: string
start: Date
end: Date
color?: string
allDay?: boolean
}
interface Props {
modelValue?: Date // selected date
view?: 'month' | 'week' | 'day' // 'month'
events?: CalendarEvent[]
weekStartsOn?: 0 | 1 // 1 (Monday)
}Emits: update:modelValue, update:view, select-date, select-range, create-event.
<Calendar
v-model="selectedDate"
v-model:view="view"
:events="meetings"
@create-event="openComposer"
/>FileInput
Drop zone + click-to-browse.
interface Props {
modelValue?: File | File[] | null
accept?: string // e.g. '.csv,image/*'
multiple?: boolean
disabled?: boolean
}<FileInput v-model="file" accept="image/*" />
<FileInput v-model="files" accept=".csv" multiple />ToggleGroup
Segmented control / button group. Single or multi.
interface ToggleOption { label: string; value: string; icon?: string; disabled?: boolean }
interface Props {
modelValue?: string | string[]
options: ToggleOption[] | string[]
multiple?: boolean
size?: 'xs' | 'sm' | 'md'
disabled?: boolean
}<ToggleGroup
v-model="align"
:options="[
{ value: 'left', icon: 'i-lucide-align-left' },
{ value: 'center', icon: 'i-lucide-align-center' },
{ value: 'right', icon: 'i-lucide-align-right' },
]"
/>FormField
Wraps a control with label / description / help / error. Use layout="row" for inline label + control (design-panel style).
interface Props {
label?: string
name?: string
description?: string
help?: string
error?: string
required?: boolean
layout?: 'col' | 'row' // 'col'
}<FormField label="Email" required :error="errors.email">
<Input v-model="form.email" type="email" />
</FormField>
<FormField label="Width" layout="row" help="In pixels">
<Input v-model="form.width" type="number" suffix="px" />
</FormField>