Search for a command to run...
Combine labels, controls, and help text to compose accessible form fields and grouped inputs.
npx shadcn@latest add @zaidan/fieldpnpx shadcn add @zaidan/fieldyarn dlx shadcn@latest add @zaidan/fieldbunx shadcn@latest add @zaidan/fieldCopy and paste the following code into your project.
1import { cva, type VariantProps } from "class-variance-authority";2import type { ComponentProps, JSX } from "solid-js";3import { createMemo, For, Show, splitProps } from "solid-js";4
5import { cn } from "~/lib/utils";6import { Label } from "~/components/ui/label";7import { Separator } from "~/components/ui/separator";8
9type FieldSetProps = ComponentProps<"fieldset"> & {10 class?: string | undefined;11};12
13const FieldSet = (props: FieldSetProps) => {14 const [local, others] = splitProps(props, ["class"]);15 return (16 <fieldset17 data-slot="field-set"18 class={cn("z-field-set flex flex-col", local.class)}19 {...others}20 />21 );22};23
24type FieldLegendProps = ComponentProps<"legend"> & {25 class?: string | undefined;26 variant?: "legend" | "label";27};28
29const FieldLegend = (props: FieldLegendProps) => {30 const [local, others] = splitProps(props, ["class", "variant"]);31 return (32 <legend33 data-slot="field-legend"34 data-variant={local.variant ?? "legend"}35 class={cn("z-field-legend", local.class)}36 {...others}37 />38 );39};40
41type FieldGroupProps = ComponentProps<"div"> & {42 class?: string | undefined;43};44
45const FieldGroup = (props: FieldGroupProps) => {46 const [local, others] = splitProps(props, ["class"]);47 return (48 <div49 data-slot="field-group"50 class={cn(51 "group/field-group @container/field-group z-field-group flex w-full flex-col",52 local.class,53 )}54 {...others}55 />56 );57};58
59const fieldVariants = cva("group/field z-field flex w-full", {60 variants: {61 orientation: {62 vertical: "z-field-orientation-vertical flex-col *:w-full [&>.sr-only]:w-auto",63 horizontal:64 "z-field-orientation-horizontal flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",65 responsive:66 "z-field-orientation-responsive @md/field-group:flex-row flex-col @md/field-group:items-center *:w-full @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",67 },68 },69 defaultVariants: {70 orientation: "vertical",71 },72});73
74type FieldProps = ComponentProps<"div"> &75 VariantProps<typeof fieldVariants> & {76 class?: string | undefined;77 };78
79const Field = (props: FieldProps) => {80 const [local, others] = splitProps(props, ["class", "orientation"]);81 return (82 // biome-ignore lint/a11y/useSemanticElements: role="group" is intentional per shadcn design for accessibility83 <div84 role="group"85 data-slot="field"86 data-orientation={local.orientation ?? "vertical"}87 class={cn(fieldVariants({ orientation: local.orientation }), local.class)}88 {...others}89 />90 );91};92
93type FieldContentProps = ComponentProps<"div"> & {94 class?: string | undefined;95};96
97const FieldContent = (props: FieldContentProps) => {98 const [local, others] = splitProps(props, ["class"]);99 return (100 <div101 data-slot="field-content"102 class={cn(103 "group/field-content z-field-content flex flex-1 flex-col leading-snug",104 local.class,105 )}106 {...others}107 />108 );109};110
111type FieldLabelProps = ComponentProps<typeof Label> & {112 class?: string | undefined;113};114
115const FieldLabel = (props: FieldLabelProps) => {116 const [local, others] = splitProps(props, ["class"]);117 return (118 <Label119 data-slot="field-label"120 class={cn(121 "group/field-label peer/field-label z-field-label flex w-fit leading-snug",122 "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",123 local.class,124 )}125 {...others}126 />127 );128};129
130type FieldTitleProps = ComponentProps<"div"> & {131 class?: string | undefined;132};133
134const FieldTitle = (props: FieldTitleProps) => {135 const [local, others] = splitProps(props, ["class"]);136 return (137 <div138 data-slot="field-label"139 class={cn("z-field-title flex w-fit items-center leading-snug", local.class)}140 {...others}141 />142 );143};144
145type FieldDescriptionProps = ComponentProps<"p"> & {146 class?: string | undefined;147};148
149const FieldDescription = (props: FieldDescriptionProps) => {150 const [local, others] = splitProps(props, ["class"]);151 return (152 <p153 data-slot="field-description"154 class={cn(155 "z-field-description font-normal leading-normal group-has-data-[orientation=horizontal]/field:text-balance",156 "nth-last-2:-mt-1 last:mt-0",157 "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",158 local.class,159 )}160 {...others}161 />162 );163};164
165type FieldSeparatorProps = ComponentProps<"div"> & {166 class?: string | undefined;167 children?: JSX.Element;168};169
170const FieldSeparator = (props: FieldSeparatorProps) => {171 const [local, others] = splitProps(props, ["class", "children"]);172 return (173 <div174 data-slot="field-separator"175 data-content={!!local.children}176 class={cn("relative z-field-separator", local.class)}177 {...others}178 >179 <Separator class="absolute inset-0 top-1/2" />180 <Show when={local.children}>181 <span182 class="relative z-field-separator-content mx-auto block w-fit bg-background"183 data-slot="field-separator-content"184 >185 {local.children}186 </span>187 </Show>188 </div>189 );190};191
192type FieldErrorProps = ComponentProps<"div"> & {193 class?: string | undefined;194 children?: JSX.Element;195 errors?: Array<{ message?: string } | undefined>;196};197
198const FieldError = (props: FieldErrorProps) => {199 const [local, others] = splitProps(props, ["class", "children", "errors"]);200
201 const content = createMemo(() => {202 if (local.children) {203 return local.children;204 }205
206 if (!local.errors?.length) {207 return null;208 }209
210 const uniqueErrors = [211 ...new Map(local.errors.map((error) => [error?.message, error])).values(),212 ];213
214 if (uniqueErrors?.length === 1) {215 return uniqueErrors[0]?.message;216 }217
218 return (219 <ul class="ml-4 flex list-disc flex-col gap-1">220 <For each={uniqueErrors}>221 {(error) => (222 <Show when={error?.message}>223 <li>{error?.message}</li>224 </Show>225 )}226 </For>227 </ul>228 );229 });230
231 return (232 <Show when={content()}>233 <div234 role="alert"235 data-slot="field-error"236 class={cn("z-field-error font-normal", local.class)}237 {...others}238 >239 {content()}240 </div>241 </Show>242 );243};244
245export {246 Field,247 FieldLabel,248 FieldDescription,249 FieldError,250 FieldGroup,251 FieldLegend,252 FieldSeparator,253 FieldSet,254 FieldContent,255 FieldTitle,256 fieldVariants,257};The Field family is designed for composing accessible forms. A typical field is structured as follows:
1<Field>2 <FieldLabel for="input-id">Label</FieldLabel>3 {/* Input, Select, Switch, etc. */}4 <FieldDescription>Optional helper text.</FieldDescription>5 <FieldError>Validation message.</FieldError>6</Field>Field is the core wrapper for a single field.FieldContent is a flex column that groups label and description. Not required if you have no description.FieldGroup, and use FieldSet with FieldLegend for semantic grouping.orientation="horizontal" on Field to align the label and control side-by-side. Pair with FieldContent to keep descriptions aligned.orientation="responsive" for automatic column layouts inside container-aware parents. Apply @container/field-group classes on FieldGroup to switch orientations at specific breakpoints.data-invalid to Field to switch the entire block into an error state.aria-invalid on the input itself for assistive technologies.FieldError immediately after the control or inside FieldContent to keep error messages aligned with the field.1<Field data-invalid>2 <FieldLabel for="email">Email</FieldLabel>3 <Input id="email" type="email" aria-invalid />4 <FieldError>Enter a valid email address.</FieldError>5</Field>FieldSet and FieldLegend keep related controls grouped for keyboard and assistive tech users.Field outputs role="group" so nested controls inherit labeling from FieldLabel and FieldLegend when combined.FieldSeparator sparingly to ensure screen readers encounter clear section boundaries.Here are the source code of all the examples from the preview page:
import { Example, ExampleWrapper } from "@/components/example";import { Badge } from "~/components/ui/badge";import { Field, FieldDescription, FieldGroup, FieldLabel,} from "~/components/ui/field";import { Input } from "~/components/ui/input"; <InputOTPFields /> </ExampleWrapper> );}
function InputFields() { return ( <Example title="Input Fields"> <FieldGroup> <Field> <FieldLabel for="input-basic">Basic Input</FieldLabel> <Input id="input-basic" placeholder="Enter text" /> </Field> <Field> <FieldLabel for="input-with-desc">Input with Description</FieldLabel> <Input id="input-with-desc" placeholder="Enter your username" /> <FieldDescription>Choose a unique username for your account.</FieldDescription> </Field> <Field> <FieldLabel for="input-desc-first">Email Address</FieldLabel> <FieldDescription>We'll never share your email with anyone.</FieldDescription> <Input id="input-desc-first" type="email" placeholder="email@example.com" /> </Field> <Field> <FieldLabel for="input-required"> Required Field <span class="text-destructive">*</span> </FieldLabel> <Input id="input-required" placeholder="This field is required" required /> <FieldDescription>This field must be filled out.</FieldDescription> </Field> <Field> <FieldLabel for="input-disabled">Disabled Input</FieldLabel> <Input id="input-disabled" placeholder="Cannot edit" disabled /> <FieldDescription>This field is currently disabled.</FieldDescription> </Field> <Field> <FieldLabel for="input-badge"> Input with Badge{" "} <Badge variant="secondary" class="ml-auto"> Recommended </Badge> </FieldLabel> <Input id="input-badge" placeholder="Enter value" /> </Field> <Field data-invalid> <FieldLabel for="input-invalid">Invalid Input</FieldLabel> <Input id="input-invalid" placeholder="This field has an error" aria-invalid /> <FieldDescription>This field contains validation errors.</FieldDescription> </Field> <Field data-disabled> <FieldLabel for="input-disabled-field">Disabled Field</FieldLabel> <Input id="input-disabled-field" placeholder="Cannot edit" disabled /> <FieldDescription>This field is currently disabled.</FieldDescription> </Field>import { Example, ExampleWrapper } from "@/components/example";import { Field, FieldDescription, FieldGroup, FieldLabel,} from "~/components/ui/field";import { Textarea } from "~/components/ui/textarea"; </FieldGroup> </Example> );}
function TextareaFields() { return ( <Example title="Textarea Fields"> <FieldGroup> <Field> <FieldLabel for="textarea-basic">Basic Textarea</FieldLabel> <Textarea id="textarea-basic" placeholder="Enter your message" /> </Field> <Field> <FieldLabel for="textarea-comments">Comments</FieldLabel> <Textarea id="textarea-comments" placeholder="Share your thoughts..." class="min-h-[100px]" /> <FieldDescription>Maximum 500 characters allowed.</FieldDescription> </Field> <Field> <FieldLabel for="textarea-bio">Bio</FieldLabel> <FieldDescription>Tell us about yourself in a few sentences.</FieldDescription> <Textarea id="textarea-bio" placeholder="I am a..." class="min-h-[120px]" /> </Field> <Field> <FieldLabel for="textarea-desc-after">Message</FieldLabel> <Textarea id="textarea-desc-after" placeholder="Enter your message" /> <FieldDescription> Enter your message so it is long enough to test the layout. </FieldDescription> </Field> <Field data-invalid> <FieldLabel for="textarea-invalid">Invalid Textarea</FieldLabel> <Textarea id="textarea-invalid" placeholder="This field has an error" aria-invalid /> <FieldDescription>This field contains validation errors.</FieldDescription> </Field> <Field data-disabled> <FieldLabel for="textarea-disabled-field">Disabled Field</FieldLabel> <Textarea id="textarea-disabled-field" placeholder="Cannot edit" disabled /> <FieldDescription>This field is currently disabled.</FieldDescription> </Field> </FieldGroup> </Example>import { Example, ExampleWrapper } from "@/components/example";import { Field, FieldDescription, FieldGroup, FieldLabel,} from "~/components/ui/field";import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "~/components/ui/select"; );}
type SelectItemType = { label: string; value: string | null };
function SelectFields() { const basicItems: SelectItemType[] = [ { label: "Option 1", value: "option1" }, { label: "Option 2", value: "option2" }, { label: "Option 3", value: "option3" }, ]; const countryItems: SelectItemType[] = [ { label: "United States", value: "us" }, { label: "United Kingdom", value: "uk" }, { label: "Canada", value: "ca" }, ]; const timezoneItems: SelectItemType[] = [ { label: "UTC", value: "utc" }, { label: "Eastern Time", value: "est" }, { label: "Pacific Time", value: "pst" }, ]; const invalidItems: SelectItemType[] = [ { label: "Option 1", value: "option1" }, { label: "Option 2", value: "option2" }, { label: "Option 3", value: "option3" }, ]; const disabledItems: SelectItemType[] = [ { label: "Option 1", value: "option1" }, { label: "Option 2", value: "option2" }, { label: "Option 3", value: "option3" }, ];
return ( <Example title="Select Fields"> <FieldGroup> <Field> <FieldLabel for="select-basic">Basic Select</FieldLabel> <Select options={basicItems} optionValue="value" optionTextValue="label" placeholder="Choose an option" itemComponent={(props) => ( <SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem> )} > <SelectTrigger id="select-basic"> <SelectValue<SelectItemType>>{(state) => state.selectedOption().label}</SelectValue> </SelectTrigger> <SelectContent /> </Select> </Field> <Field> <FieldLabel for="select-country">Country</FieldLabel> <Select options={countryItems} optionValue="value" optionTextValue="label" placeholder="Select your country" itemComponent={(props) => ( <SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem> )} > <SelectTrigger id="select-country"> <SelectValue<SelectItemType>>{(state) => state.selectedOption().label}</SelectValue> </SelectTrigger> <SelectContent /> </Select> <FieldDescription>Select the country where you currently reside.</FieldDescription> </Field> <Field> <FieldLabel for="select-timezone">Timezone</FieldLabel> <FieldDescription>Choose your local timezone for accurate scheduling.</FieldDescription> <Select options={timezoneItems} optionValue="value" optionTextValue="label" placeholder="Select timezone" itemComponent={(props) => ( <SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem> )} > <SelectTrigger id="select-timezone"> <SelectValue<SelectItemType>>{(state) => state.selectedOption().label}</SelectValue> </SelectTrigger> <SelectContent /> </Select> </Field> <Field data-invalid> <FieldLabel for="select-invalid">Invalid Select</FieldLabel> <Select options={invalidItems} optionValue="value" optionTextValue="label" placeholder="This field has an error" itemComponent={(props) => ( <SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem> )} > <SelectTrigger id="select-invalid" aria-invalid> <SelectValue<SelectItemType>>{(state) => state.selectedOption().label}</SelectValue> </SelectTrigger> <SelectContent /> </Select> <FieldDescription>This field contains validation errors.</FieldDescription> </Field> <Field data-disabled> <FieldLabel for="select-disabled-field">Disabled Field</FieldLabel> <Select options={disabledItems} optionValue="value" optionTextValue="label" placeholder="Cannot select" disabled itemComponent={(props) => ( <SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem> )} > <SelectTrigger id="select-disabled-field"> <SelectValue<SelectItemType>>{(state) => state.selectedOption().label}</SelectValue> </SelectTrigger> <SelectContent /> </Select> <FieldDescription>This field is currently disabled.</FieldDescription> </Field>import { Example, ExampleWrapper } from "@/components/example";import { Checkbox } from "~/components/ui/checkbox";import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSet, FieldTitle,} from "~/components/ui/field"; </FieldGroup> </Example> );}
function CheckboxFields() { return ( <Example title="Checkbox Fields"> <FieldGroup> <Field orientation="horizontal"> <Checkbox id="checkbox-basic" defaultChecked /> <FieldLabel for="checkbox-basic" class="font-normal"> I agree to the terms and conditions </FieldLabel> </Field> <Field orientation="horizontal"> <FieldLabel for="checkbox-right">Accept terms and conditions</FieldLabel> <Checkbox id="checkbox-right" /> </Field> <Field orientation="horizontal"> <Checkbox id="checkbox-with-desc" /> <FieldContent> <FieldLabel for="checkbox-with-desc">Subscribe to newsletter</FieldLabel> <FieldDescription> Receive weekly updates about new features and promotions. </FieldDescription> </FieldContent> </Field> <FieldLabel for="checkbox-with-title"> <Field orientation="horizontal"> <Checkbox id="checkbox-with-title" /> <FieldContent> <FieldTitle>Enable Touch ID</FieldTitle> <FieldDescription>Enable Touch ID to quickly unlock your device.</FieldDescription> </FieldContent> </Field> </FieldLabel> <FieldSet> <FieldLegend variant="label">Preferences</FieldLegend> <FieldDescription>Select all that apply to customize your experience.</FieldDescription> <FieldGroup class="gap-3"> <Field orientation="horizontal"> <Checkbox id="pref-dark" /> <FieldLabel for="pref-dark" class="font-normal"> Dark mode </FieldLabel> </Field> <Field orientation="horizontal"> <Checkbox id="pref-compact" /> <FieldLabel for="pref-compact" class="font-normal"> Compact view </FieldLabel> </Field> <Field orientation="horizontal"> <Checkbox id="pref-notifications" /> <FieldLabel for="pref-notifications" class="font-normal"> Enable notifications </FieldLabel> </Field> </FieldGroup> </FieldSet> <Field data-invalid orientation="horizontal"> <Checkbox id="checkbox-invalid" aria-invalid /> <FieldLabel for="checkbox-invalid" class="font-normal"> Invalid checkbox </FieldLabel> </Field> <Field data-disabled orientation="horizontal"> <Checkbox id="checkbox-disabled-field" disabled /> <FieldLabel for="checkbox-disabled-field" class="font-normal"> Disabled checkbox </FieldLabel> </Field>import { Example, ExampleWrapper } from "@/components/example";import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSet, FieldTitle,} from "~/components/ui/field";import { RadioGroup, RadioGroupItem } from "~/components/ui/radio-group"; </FieldGroup> </Example> );}
function RadioFields() { return ( <Example title="Radio Fields"> <FieldGroup> <FieldSet> <FieldLegend variant="label">Subscription Plan</FieldLegend> <RadioGroup defaultValue="free"> <Field orientation="horizontal"> <RadioGroupItem value="free" id="radio-free" /> <FieldLabel for="radio-free" class="font-normal"> Free Plan </FieldLabel> </Field> <Field orientation="horizontal"> <RadioGroupItem value="pro" id="radio-pro" /> <FieldLabel for="radio-pro" class="font-normal"> Pro Plan </FieldLabel> </Field> <Field orientation="horizontal"> <RadioGroupItem value="enterprise" id="radio-enterprise" /> <FieldLabel for="radio-enterprise" class="font-normal"> Enterprise </FieldLabel> </Field> </RadioGroup> </FieldSet> <FieldSet> <FieldLegend variant="label">Battery Level</FieldLegend> <FieldDescription>Choose your preferred battery level.</FieldDescription> <RadioGroup> <Field orientation="horizontal"> <RadioGroupItem value="high" id="battery-high" /> <FieldLabel for="battery-high">High</FieldLabel> </Field> <Field orientation="horizontal"> <RadioGroupItem value="medium" id="battery-medium" /> <FieldLabel for="battery-medium">Medium</FieldLabel> </Field> <Field orientation="horizontal"> <RadioGroupItem value="low" id="battery-low" /> <FieldLabel for="battery-low">Low</FieldLabel> </Field> </RadioGroup> </FieldSet> <RadioGroup class="gap-6"> <Field orientation="horizontal"> <RadioGroupItem value="option1" id="radio-content-1" /> <FieldContent> <FieldLabel for="radio-content-1">Enable Touch ID</FieldLabel> <FieldDescription>Enable Touch ID to quickly unlock your device.</FieldDescription> </FieldContent> </Field> <Field orientation="horizontal"> <RadioGroupItem value="option2" id="radio-content-2" /> <FieldContent> <FieldLabel for="radio-content-2"> Enable Touch ID and Face ID to make it even faster to unlock your device. This is a long label to test the layout. </FieldLabel> <FieldDescription>Enable Touch ID to quickly unlock your device.</FieldDescription> </FieldContent> </Field> </RadioGroup> <RadioGroup class="gap-3"> <FieldLabel for="radio-title-1"> <Field orientation="horizontal"> <RadioGroupItem value="title1" id="radio-title-1" /> <FieldContent> <FieldTitle>Enable Touch ID</FieldTitle> <FieldDescription>Enable Touch ID to quickly unlock your device.</FieldDescription> </FieldContent> </Field> </FieldLabel> <FieldLabel for="radio-title-2"> <Field orientation="horizontal"> <RadioGroupItem value="title2" id="radio-title-2" /> <FieldContent> <FieldTitle> Enable Touch ID and Face ID to make it even faster to unlock your device. This is a long label to test the layout. </FieldTitle> <FieldDescription>Enable Touch ID to quickly unlock your device.</FieldDescription> </FieldContent> </Field> </FieldLabel> </RadioGroup> <FieldSet> <FieldLegend variant="label">Invalid Radio Group</FieldLegend> <RadioGroup> <Field data-invalid orientation="horizontal"> <RadioGroupItem value="invalid1" id="radio-invalid-1" aria-invalid /> <FieldLabel for="radio-invalid-1">Invalid Option 1</FieldLabel> </Field> <Field data-invalid orientation="horizontal"> <RadioGroupItem value="invalid2" id="radio-invalid-2" aria-invalid /> <FieldLabel for="radio-invalid-2">Invalid Option 2</FieldLabel> </Field> </RadioGroup> </FieldSet> <FieldSet> <FieldLegend variant="label">Disabled Radio Group</FieldLegend> <RadioGroup disabled> <Field data-disabled orientation="horizontal"> <RadioGroupItem value="disabled1" id="radio-disabled-1" disabled /> <FieldLabel for="radio-disabled-1">Disabled Option 1</FieldLabel> </Field> <Field data-disabled orientation="horizontal"> <RadioGroupItem value="disabled2" id="radio-disabled-2" disabled /> <FieldLabel for="radio-disabled-2">Disabled Option 2</FieldLabel> </Field> </RadioGroup> </FieldSet>import { Example, ExampleWrapper } from "@/components/example";import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel,} from "~/components/ui/field";import { Switch } from "~/components/ui/switch"; </FieldGroup> </Example> );}
function SwitchFields() { return ( <Example title="Switch Fields"> <FieldGroup> <Field orientation="horizontal"> <FieldContent> <FieldLabel for="switch-airplane">Airplane Mode</FieldLabel> <FieldDescription>Turn on airplane mode to disable all connections.</FieldDescription> </FieldContent> <Switch id="switch-airplane" /> </Field> <Field orientation="horizontal"> <FieldLabel for="switch-dark">Dark Mode</FieldLabel> <Switch id="switch-dark" /> </Field> <Field orientation="horizontal"> <Switch id="switch-marketing" /> <FieldContent> <FieldLabel for="switch-marketing">Marketing Emails</FieldLabel> <FieldDescription> Receive emails about new products, features, and more. </FieldDescription> </FieldContent> </Field> <Field> <FieldLabel>Privacy Settings</FieldLabel> <FieldDescription>Manage your privacy preferences.</FieldDescription> <Field orientation="horizontal"> <Switch id="switch-profile" defaultChecked /> <FieldContent> <FieldLabel for="switch-profile" class="font-normal"> Make profile visible to others </FieldLabel> </FieldContent> </Field> <Field orientation="horizontal"> <Switch id="switch-email" /> <FieldContent> <FieldLabel for="switch-email" class="font-normal"> Show email on profile </FieldLabel> </FieldContent> </Field> </Field> <Field data-invalid orientation="horizontal"> <FieldContent> <FieldLabel for="switch-invalid">Invalid Switch</FieldLabel> <FieldDescription>This switch has validation errors.</FieldDescription> </FieldContent> <Switch id="switch-invalid" aria-invalid /> </Field> <Field data-disabled orientation="horizontal"> <FieldContent> <FieldLabel for="switch-disabled-field">Disabled Switch</FieldLabel> <FieldDescription>This switch is currently disabled.</FieldDescription> </FieldContent> <Switch id="switch-disabled-field" disabled /> </Field>import { createSignal } from "solid-js";import { Example, ExampleWrapper } from "@/components/example";import { Field, FieldDescription, FieldGroup, FieldLabel,} from "~/components/ui/field";import { Slider } from "~/components/ui/slider"; </FieldGroup> </Example> );}
function SliderFields() { const [volume, setVolume] = createSignal([50]); const [brightness, setBrightness] = createSignal([75]); const [temperature, setTemperature] = createSignal([0.3, 0.7]); const [priceRange, setPriceRange] = createSignal([25, 75]); const [colorBalance, setColorBalance] = createSignal([10, 20, 70]);
return ( <Example title="Slider Fields"> <FieldGroup> <Field> <FieldLabel for="slider-volume">Volume</FieldLabel> <Slider id="slider-volume" value={volume()} onChange={setVolume} maxValue={100} step={1} /> </Field> <Field> <FieldLabel for="slider-brightness">Screen Brightness</FieldLabel> <Slider id="slider-brightness" value={brightness()} onChange={setBrightness} maxValue={100} step={5} /> <FieldDescription>Current brightness: {brightness()[0]}%</FieldDescription> </Field> <Field> <FieldLabel for="slider-quality">Video Quality</FieldLabel> <FieldDescription>Higher quality uses more bandwidth.</FieldDescription> <Slider id="slider-quality" defaultValue={[720]} maxValue={1080} minValue={360} step={360} /> </Field> <Field> <FieldLabel for="slider-temperature">Temperature Range</FieldLabel> <Slider id="slider-temperature" value={temperature()} onChange={setTemperature} minValue={0} maxValue={1} step={0.1} /> <FieldDescription> Range: {temperature()[0].toFixed(1)} - {temperature()[1].toFixed(1)} </FieldDescription> </Field> <Field> <FieldLabel for="slider-price-range">Price Range</FieldLabel> <Slider id="slider-price-range" value={priceRange()} onChange={setPriceRange} maxValue={100} step={5} /> <FieldDescription> ${priceRange()[0]} - ${priceRange()[1]} </FieldDescription> </Field> <Field> <FieldLabel for="slider-color-balance">Color Balance</FieldLabel> <Slider id="slider-color-balance" value={colorBalance()} onChange={setColorBalance} maxValue={100} step={10} /> <FieldDescription> Red: {colorBalance()[0]}%, Green: {colorBalance()[1]}%, Blue: {colorBalance()[2]}% </FieldDescription> </Field> <Field data-invalid> <FieldLabel for="slider-invalid">Invalid Slider</FieldLabel> <Slider id="slider-invalid" defaultValue={[30]} maxValue={100} aria-invalid /> <FieldDescription>This slider has validation errors.</FieldDescription> </Field> <Field data-disabled> <FieldLabel for="slider-disabled-field">Disabled Slider</FieldLabel> <Slider id="slider-disabled-field" defaultValue={[50]} maxValue={100} disabled /> <FieldDescription>This slider is currently disabled.</FieldDescription> </Field>import { Example, ExampleWrapper } from "@/components/example";import { Field, FieldDescription, FieldGroup, FieldLabel,} from "~/components/ui/field";import { NativeSelect, NativeSelectOptGroup, NativeSelectOption,} from "~/components/ui/native-select"; </FieldGroup> </Example> );}
function NativeSelectFields() { return ( <Example title="Native Select Fields"> <FieldGroup> <Field> <FieldLabel for="native-select-basic">Basic Native Select</FieldLabel> <NativeSelect id="native-select-basic"> <NativeSelectOption value="">Choose an option</NativeSelectOption> <NativeSelectOption value="option1">Option 1</NativeSelectOption> <NativeSelectOption value="option2">Option 2</NativeSelectOption> <NativeSelectOption value="option3">Option 3</NativeSelectOption> </NativeSelect> </Field> <Field> <FieldLabel for="native-select-country">Country</FieldLabel> <NativeSelect id="native-select-country"> <NativeSelectOption value="">Select your country</NativeSelectOption> <NativeSelectOption value="us">United States</NativeSelectOption> <NativeSelectOption value="uk">United Kingdom</NativeSelectOption> <NativeSelectOption value="ca">Canada</NativeSelectOption> </NativeSelect> <FieldDescription>Select the country where you currently reside.</FieldDescription> </Field> <Field> <FieldLabel for="native-select-timezone">Timezone</FieldLabel> <FieldDescription>Choose your local timezone for accurate scheduling.</FieldDescription> <NativeSelect id="native-select-timezone"> <NativeSelectOption value="">Select timezone</NativeSelectOption> <NativeSelectOption value="utc">UTC</NativeSelectOption> <NativeSelectOption value="est">Eastern Time</NativeSelectOption> <NativeSelectOption value="pst">Pacific Time</NativeSelectOption> </NativeSelect> </Field> <Field> <FieldLabel for="native-select-grouped">Grouped Options</FieldLabel> <NativeSelect id="native-select-grouped"> <NativeSelectOption value="">Select a region</NativeSelectOption> <NativeSelectOptGroup label="North America"> <NativeSelectOption value="us">United States</NativeSelectOption> <NativeSelectOption value="ca">Canada</NativeSelectOption> <NativeSelectOption value="mx">Mexico</NativeSelectOption> </NativeSelectOptGroup> <NativeSelectOptGroup label="Europe"> <NativeSelectOption value="uk">United Kingdom</NativeSelectOption> <NativeSelectOption value="fr">France</NativeSelectOption> <NativeSelectOption value="de">Germany</NativeSelectOption> </NativeSelectOptGroup> </NativeSelect> <FieldDescription>Native select with grouped options using optgroup.</FieldDescription> </Field> <Field data-invalid> <FieldLabel for="native-select-invalid">Invalid Native Select</FieldLabel> <NativeSelect id="native-select-invalid" aria-invalid> <NativeSelectOption value="">This field has an error</NativeSelectOption> <NativeSelectOption value="option1">Option 1</NativeSelectOption> <NativeSelectOption value="option2">Option 2</NativeSelectOption> <NativeSelectOption value="option3">Option 3</NativeSelectOption> </NativeSelect> <FieldDescription>This field contains validation errors.</FieldDescription> </Field> <Field data-disabled> <FieldLabel for="native-select-disabled-field">Disabled Field</FieldLabel> <NativeSelect id="native-select-disabled-field" disabled> <NativeSelectOption value="">Cannot select</NativeSelectOption> <NativeSelectOption value="option1">Option 1</NativeSelectOption> <NativeSelectOption value="option2">Option 2</NativeSelectOption> <NativeSelectOption value="option3">Option 3</NativeSelectOption> </NativeSelect> <FieldDescription>This field is currently disabled.</FieldDescription> </Field>import { createSignal } from "solid-js";import { Example, ExampleWrapper } from "@/components/example";import { Field, FieldDescription, FieldGroup, FieldLabel,} from "~/components/ui/field";import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,} from "~/components/ui/input-otp"; </FieldGroup> </Example> );}
function InputOTPFields() { const [value, setValue] = createSignal(""); const [pinValue, setPinValue] = createSignal("");
return ( <Example title="OTP Input Fields"> <FieldGroup> <Field> <FieldLabel for="otp-basic">Verification Code</FieldLabel> <InputOTP id="otp-basic" maxLength={6}> <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> <InputOTPSlot index={3} /> <InputOTPSlot index={4} /> <InputOTPSlot index={5} /> </InputOTPGroup> </InputOTP> </Field> <Field> <FieldLabel for="otp-with-desc">Enter OTP</FieldLabel> <InputOTP id="otp-with-desc" maxLength={6} value={value()} onValueChange={setValue}> <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> <InputOTPSlot index={3} /> <InputOTPSlot index={4} /> <InputOTPSlot index={5} /> </InputOTPGroup> </InputOTP> <FieldDescription>Enter the 6-digit code sent to your email.</FieldDescription> </Field> <Field> <FieldLabel for="otp-separator">Two-Factor Authentication</FieldLabel> <InputOTP id="otp-separator" maxLength={6}> <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> </InputOTPGroup> <InputOTPSeparator /> <InputOTPGroup> <InputOTPSlot index={3} /> <InputOTPSlot index={4} /> <InputOTPSlot index={5} /> </InputOTPGroup> </InputOTP> <FieldDescription>Enter the code from your authenticator app.</FieldDescription> </Field> <Field> <FieldLabel for="otp-pin">PIN Code</FieldLabel> <InputOTP id="otp-pin" maxLength={4} value={pinValue()} onValueChange={setPinValue}> <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> <InputOTPSlot index={3} /> </InputOTPGroup> </InputOTP> <FieldDescription>Enter your 4-digit PIN (numbers only).</FieldDescription> </Field> <Field data-invalid> <FieldLabel for="otp-invalid">Invalid OTP</FieldLabel> <InputOTP id="otp-invalid" maxLength={6}> <InputOTPGroup> <InputOTPSlot index={0} aria-invalid /> <InputOTPSlot index={1} aria-invalid /> <InputOTPSlot index={2} aria-invalid /> <InputOTPSlot index={3} aria-invalid /> <InputOTPSlot index={4} aria-invalid /> <InputOTPSlot index={5} aria-invalid /> </InputOTPGroup> </InputOTP> <FieldDescription>This OTP field contains validation errors.</FieldDescription> </Field> <Field data-disabled> <FieldLabel for="otp-disabled-field">Disabled OTP</FieldLabel> <InputOTP id="otp-disabled-field" maxLength={6} disabled> <InputOTPGroup> <InputOTPSlot index={0} /> <InputOTPSlot index={1} /> <InputOTPSlot index={2} /> <InputOTPSlot index={3} /> <InputOTPSlot index={4} /> <InputOTPSlot index={5} /> </InputOTPGroup> </InputOTP> <FieldDescription>This OTP field is currently disabled.</FieldDescription>Container that renders a semantic fieldset with spacing presets.
| Prop | Type | Default |
|---|---|---|
class | string |
Legend element for a FieldSet. Switch to the label variant to align with label sizing.
| Prop | Type | Default |
|---|---|---|
variant | "legend" | "label" | "legend" |
class | string |
Layout wrapper that stacks Field components and enables container queries for responsive orientations.
| Prop | Type | Default |
|---|---|---|
class | string |
The core wrapper for a single field. Provides orientation control, invalid state styling, and spacing.
| Prop | Type | Default |
|---|---|---|
orientation | "vertical" | "horizontal" | "responsive" | "vertical" |
class | string | |
data-invalid | boolean |
Flex column that groups control and descriptions when the label sits beside the control.
| Prop | Type | Default |
|---|---|---|
class | string |
Label styled for both direct inputs and nested Field children.
| Prop | Type | Default |
|---|---|---|
class | string |
Renders a title with label styling inside FieldContent.
| Prop | Type | Default |
|---|---|---|
class | string |
Helper text slot that automatically balances long lines in horizontal layouts.
| Prop | Type | Default |
|---|---|---|
class | string |
Visual divider to separate sections inside a FieldGroup. Accepts optional inline content.
| Prop | Type | Default |
|---|---|---|
class | string | |
children | JSX.Element |
Accessible error container that accepts children or an errors array.
| Prop | Type | Default |
|---|---|---|
errors | Array<{ message?: string } | undefined> | |
class | string |
When the errors array contains multiple messages, the component renders a list automatically.