Field
Layout primitive that stacks a label, control, description, and error into a single semantic group. No motion — just a stable rhythm that survives invalid states without reflow.
Usage
We'll only use this to send the login code.
<script lang="ts">
import * as Field from '$lib/components/ui/field';
import { Input } from '$lib/components/ui/input';
</script>
<Field.Field>
<Field.Label for="email">Email</Field.Label>
<Input id="email" type="email" placeholder="you@bloom.social" aria-invalid="true" />
<Field.Description>We'll only use this to send the login code.</Field.Description>
<Field.Error>Enter a valid email address.</Field.Error>
</Field.Field>Orientations
Shown on comments and commits.
<script lang="ts" module>
import { tv, type VariantProps } from 'tailwind-variants';
export const fieldVariants = tv({
base: 'data-[invalid=true]:text-destructive gap-3 group/field flex w-full',
variants: {
orientation: {
vertical: 'flex-col [&>*]:w-full [&>.sr-only]:w-auto',
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',
responsive:
'flex-col @md/field-group:flex-row @md/field-group:items-center @md/field-group:has-[>[data-slot=field-content]]:items-start [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto @md/field-group:[&>[data-slot=field-label]]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px'
}
},
defaultVariants: {
orientation: 'vertical'
}
});
export type FieldOrientation = VariantProps<typeof fieldVariants>['orientation'];
</script>
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
orientation = 'vertical',
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
orientation?: FieldOrientation;
} = $props();
</script>
<div
bind:this={ref}
role="group"
data-slot="field"
data-orientation={orientation}
class={cn(fieldVariants({ orientation }), className)}
{...restProps}
>
{@render children?.()}
</div>
FieldGroup composition
Markdown is supported.
<script lang="ts" module>
import { tv, type VariantProps } from 'tailwind-variants';
export const fieldVariants = tv({
base: 'data-[invalid=true]:text-destructive gap-3 group/field flex w-full',
variants: {
orientation: {
vertical: 'flex-col [&>*]:w-full [&>.sr-only]:w-auto',
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',
responsive:
'flex-col @md/field-group:flex-row @md/field-group:items-center @md/field-group:has-[>[data-slot=field-content]]:items-start [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto @md/field-group:[&>[data-slot=field-label]]:flex-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px'
}
},
defaultVariants: {
orientation: 'vertical'
}
});
export type FieldOrientation = VariantProps<typeof fieldVariants>['orientation'];
</script>
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
orientation = 'vertical',
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
orientation?: FieldOrientation;
} = $props();
</script>
<div
bind:this={ref}
role="group"
data-slot="field"
data-orientation={orientation}
class={cn(fieldVariants({ orientation }), className)}
{...restProps}
>
{@render children?.()}
</div>
Select
Field wrapping a Select dropdown.
The framework used in this project.
<script lang="ts">
import * as Field from '$lib/components/ui/field';
import * as Select from '$lib/components/ui/select';
import { Select as SelectPrimitive } from 'bits-ui';
</script>
<Field.Field>
<Field.Label>Framework</Field.Label>
<Select.Root type="single">
<Select.Trigger class="w-full">
<SelectPrimitive.Value placeholder="Pick a framework" />
</Select.Trigger>
<Select.Content>
<Select.Item value="svelte" label="Svelte" />
<Select.Item value="react" label="React" />
<Select.Item value="vue" label="Vue" />
<Select.Item value="solid" label="Solid" />
</Select.Content>
</Select.Root>
<Field.Description>The framework used in this project.</Field.Description>
</Field.Field>Slider
Field wrapping a Slider range control.
Drag to adjust the output level.
<script lang="ts">
import * as Field from '$lib/components/ui/field';
import { Slider } from '$lib/components/ui/slider';
</script>
<Field.Field>
<Field.Label>Volume</Field.Label>
<Slider type="single" value={50} min={0} max={100} step={1} />
<Field.Description>Drag to adjust the output level.</Field.Description>
</Field.Field>Fieldset
Field.Set groups related fields under a shared legend — ideal for radio or checkbox clusters.
<script lang="ts">
import * as Field from '$lib/components/ui/field';
import * as RadioGroup from '$lib/components/ui/radio-group';
</script>
<Field.Set>
<Field.Legend>Payment method</Field.Legend>
<RadioGroup.Root value="card" class="gap-2">
<div class="flex items-center gap-2">
<RadioGroup.Item id="pay-card" value="card" />
<Field.Label for="pay-card">Card</Field.Label>
</div>
<div class="flex items-center gap-2">
<RadioGroup.Item id="pay-paypal" value="paypal" />
<Field.Label for="pay-paypal">PayPal</Field.Label>
</div>
<div class="flex items-center gap-2">
<RadioGroup.Item id="pay-apple" value="apple" />
<Field.Label for="pay-apple">Apple Pay</Field.Label>
</div>
</RadioGroup.Root>
</Field.Set>Checkbox
Horizontal Field layout places the label to the right of the Checkbox.
You agree to our Terms of Service and Privacy Policy.
<script lang="ts">
import * as Field from '$lib/components/ui/field';
import { Checkbox } from '$lib/components/ui/checkbox';
</script>
<Field.Field orientation="horizontal">
<Checkbox id="terms" />
<div class="flex flex-col gap-0.5">
<Field.Label for="terms">Accept terms and conditions</Field.Label>
<Field.Description>You agree to our Terms of Service and Privacy Policy.</Field.Description>
</div>
</Field.Field>Radio
Field composing a RadioGroup with a shared label and description.
Choose which events trigger a notification.
<script lang="ts">
import * as Field from '$lib/components/ui/field';
import * as RadioGroup from '$lib/components/ui/radio-group';
</script>
<Field.Field>
<Field.Label>Notify me about</Field.Label>
<RadioGroup.Root value="all" class="gap-2">
<div class="flex items-center gap-2">
<RadioGroup.Item id="notify-all" value="all" />
<Field.Label for="notify-all">All activity</Field.Label>
</div>
<div class="flex items-center gap-2">
<RadioGroup.Item id="notify-mentions" value="mentions" />
<Field.Label for="notify-mentions">Mentions only</Field.Label>
</div>
<div class="flex items-center gap-2">
<RadioGroup.Item id="notify-none" value="none" />
<Field.Label for="notify-none">Nothing</Field.Label>
</div>
</RadioGroup.Root>
<Field.Description>Choose which events trigger a notification.</Field.Description>
</Field.Field>Switch
Horizontal Field layout places label and description to the right of the Switch.
Receive updates about new features and offers.
<script lang="ts">
import * as Field from '$lib/components/ui/field';
import { Switch } from '$lib/components/ui/switch';
</script>
<Field.Field orientation="horizontal">
<Switch id="marketing" />
<div class="flex flex-col gap-0.5">
<Field.Label for="marketing">Marketing emails</Field.Label>
<Field.Description>Receive updates about new features and offers.</Field.Description>
</div>
</Field.Field>Field.Field props
Renders a role=group div. Orientation drives how label, control, description, and error lay out. Sub-components (Label, Description, Error, Content, Title, Legend, Separator, Set) render their intended slot with tokenized spacing.
| Prop | Type | Default | Description |
|---|---|---|---|
'vertical' | 'horizontal' | 'responsive' | 'vertical' | Stack direction. Responsive flips to horizontal at the container's @md breakpoint. | |
HTMLDivElement | null | null | Two-way-bindable element reference. | |
string | — | Merged onto the root via tailwind-merge. |
Field.Error props
Renders a role=alert region only when there is content — absent by default so no layout is reserved.
| Prop | Type | Default | Description |
|---|---|---|---|
{ message?: string }[] | — | Optional list of error descriptors. One message renders inline; many render as a list. | |
Snippet | — | Custom error markup. Overrides the errors array when provided. | |
string | — | Merged onto the alert via tailwind-merge. |