Combine labels, controls, and help text to compose accessible form fields and grouped inputs.
Installation
pnpm dlx sprawlify@latest add fieldnpx sprawlify@latest add fieldyarn sprawlify@latest add fieldbunx --bun sprawlify@latest add field
Install the following dependencies:
pnpm add @sprawlify/primitives @sprawlify/sveltenpm install @sprawlify/primitives @sprawlify/svelteyarn add @sprawlify/primitives @sprawlify/sveltebun add @sprawlify/primitives @sprawlify/svelte
Add the following files to your project:
1<script lang="ts">
2import { cn } from "@/lib/utils"
3import { Sprawlify, type PolymorphicProps } from "@sprawlify/svelte"
4import type { HTMLAttributes } from "svelte/elements"
5
6interface Props extends HTMLAttributes<HTMLDivElement>, PolymorphicProps<"div"> {}
7
8let { class: className, children, ...rest }: Props = $props()
9</script>
10
11<Sprawlify
12 as="div"
13 data-scope="field"
14 data-part="content"
15 data-slot="field-content"
16 class={cn(
17 "group/field-content flex flex-1 flex-col gap-0.5 leading-snug",
18 className
19 )}
20 {...rest}
21>
22 {@render children?.()}
23</Sprawlify>
241<script lang="ts">
2import { cn } from "@/lib/utils"
3import { Field as FieldPrimitive } from "@sprawlify/svelte/field"
4import type { ComponentProps } from "svelte"
5
6interface Props extends ComponentProps<typeof FieldPrimitive.HelperText> {}
7
8let { class: className, children, ...rest }: Props = $props()
9</script>
10
11<FieldPrimitive.HelperText
12 data-slot="field-description"
13 class={cn(
14 "text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
15 "last:mt-0 nth-last-2:-mt-1",
16 "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
17 className
18 )}
19 {...rest}
20>
21 {@render children?.()}
22</FieldPrimitive.HelperText>
231<script lang="ts">
2import { cn } from "@/lib/utils"
3import { Field as FieldPrimitive } from "@sprawlify/svelte/field"
4import { Sprawlify } from "@sprawlify/svelte"
5import type { ComponentProps } from "svelte"
6
7interface Props extends ComponentProps<typeof FieldPrimitive.ErrorText> {
8 errors?: Array<{ message?: string } | undefined>
9}
10
11let { class: className, children, errors, ...rest }: Props = $props()
12
13const content = $derived.by(() => {
14 if (children) return children
15
16 if (!errors?.length) return null
17
18 const uniqueErrors = [
19 ...new Map(errors.map((error) => [error?.message, error])).values(),
20 ]
21
22 return uniqueErrors
23})
24</script>
25
26{#if content}
27 <FieldPrimitive.ErrorText
28 data-slot="field-error"
29 class={cn("text-sm font-normal text-destructive", className)}
30 {...rest}
31 >
32 {#if children}
33 {@render children()}
34 {:else if Array.isArray(content)}
35 {#if content.length === 1}
36 {content[0]?.message}
37 {:else}
38 <Sprawlify as="ul" class="ml-4 flex list-disc flex-col gap-1">
39 {#each content as error, i (i)}
40 {#if error?.message}
41 <Sprawlify as="li">{error.message}</Sprawlify>
42 {/if}
43 {/each}
44 </Sprawlify>
45 {/if}
46 {/if}
47 </FieldPrimitive.ErrorText>
48{/if}
491<script lang="ts">
2import { cn } from "@/lib/utils"
3import { Sprawlify, type PolymorphicProps } from "@sprawlify/svelte"
4import type { HTMLAttributes } from "svelte/elements"
5
6interface Props extends HTMLAttributes<HTMLDivElement>, PolymorphicProps<"div"> {}
7
8let { class: className, children, ...rest }: Props = $props()
9</script>
10
11<Sprawlify
12 as="div"
13 data-scope="field"
14 data-part="group"
15 data-slot="field-group"
16 class={cn(
17 "group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
18 className
19 )}
20 {...rest}
21>
22 {@render children?.()}
23</Sprawlify>
241<script lang="ts">
2import { cn } from "@/lib/utils"
3import { Field as FieldPrimitive } from "@sprawlify/svelte/field"
4import type { ComponentProps } from "svelte"
5
6interface Props extends ComponentProps<typeof FieldPrimitive.Label> {}
7
8let { class: className, children, ...rest }: Props = $props()
9</script>
10
11<FieldPrimitive.Label
12 data-slot="field-label"
13 class={cn(
14 "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-[state=checked]:border-primary/30 has-data-[state=checked]:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-[state=checked]:border-primary/20 dark:has-data-[state=checked]:bg-primary/10",
15 "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
16 className
17 )}
18 {...rest}
19>
20 {@render children?.()}
21</FieldPrimitive.Label>
221<script lang="ts">
2import { cn } from "@/lib/utils"
3import { Fieldset as FieldsetPrimitive } from "@sprawlify/svelte/fieldset"
4import type { ComponentProps } from "svelte"
5
6interface Props extends ComponentProps<typeof FieldsetPrimitive.Legend> {
7 variant?: "legend" | "label"
8}
9
10let { class: className, variant = "legend", children, ...rest }: Props = $props()
11</script>
12
13<FieldsetPrimitive.Legend
14 data-slot="field-legend"
15 data-variant={variant}
16 class={cn(
17 "mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
18 className
19 )}
20 {...rest}
21>
22 {@render children?.()}
23</FieldsetPrimitive.Legend>
241<script lang="ts">
2import { cn } from "@/lib/utils"
3import { Field as FieldPrimitive } from "@sprawlify/svelte/field"
4import type { ComponentProps } from "svelte"
5
6interface Props extends ComponentProps<typeof FieldPrimitive.RequiredIndicator> {}
7
8let { class: className, children, ...rest }: Props = $props()
9</script>
10
11<FieldPrimitive.RequiredIndicator
12 data-slot="field-required-indicator"
13 class={cn("text-destructive", className)}
14 {...rest}
15>
16 {@render children?.()}
17</FieldPrimitive.RequiredIndicator>
181<script lang="ts">
2import { cn } from "@/lib/utils"
3import { Separator } from "@/components/ui/separator"
4import { Sprawlify, type PolymorphicProps } from "@sprawlify/svelte"
5import type { Snippet } from "svelte"
6import type { HTMLAttributes } from "svelte/elements";
7
8interface Props extends HTMLAttributes<HTMLDivElement>, PolymorphicProps<"div"> {
9 children?: Snippet
10}
11
12let { class: className, children, ...rest }: Props = $props()
13</script>
14
15<Sprawlify
16 as="div"
17 data-scope="field"
18 data-part="separator"
19 data-slot="field-separator"
20 data-content={!!children}
21 class={cn(
22 "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
23 className
24 )}
25 {...rest}
26>
27 <Separator class="absolute inset-0 top-1/2" />
28 {#if children}
29 <Sprawlify
30 as="span"
31 class="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
32 data-scope="field"
33 data-part="separator-content"
34 data-slot="field-separator-content"
35 >
36 {@render children()}
37 </Sprawlify>
38 {/if}
39</Sprawlify>
401<script lang="ts">
2import { cn } from "@/lib/utils"
3import { Fieldset as FieldsetPrimitive } from "@sprawlify/svelte/fieldset"
4import type { ComponentProps } from "svelte"
5
6interface Props extends ComponentProps<typeof FieldsetPrimitive.Root> {}
7
8let { class: className, children, ...rest }: Props = $props()
9</script>
10
11<FieldsetPrimitive.Root
12 data-slot="field-set"
13 class={cn(
14 "flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
15 className
16 )}
17 {...rest}
18>
19 {@render children?.()}
20</FieldsetPrimitive.Root>
211<script lang="ts">
2import { cn } from "@/lib/utils"
3import { Sprawlify, type PolymorphicProps } from "@sprawlify/svelte"
4import type { HTMLAttributes } from "svelte/elements"
5
6interface Props extends HTMLAttributes<HTMLDivElement>, PolymorphicProps<"div"> {}
7
8let { class: className, children, ...rest }: Props = $props()
9</script>
10
11<Sprawlify
12 as="div"
13 data-scope="field"
14 data-part="title"
15 data-slot="field-title"
16 class={cn(
17 "flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
18 className
19 )}
20 {...rest}
21>
22 {@render children?.()}
23</Sprawlify>
241<script lang="ts">
2import { cn } from "@/lib/utils"
3import { Field as FieldPrimitive } from "@sprawlify/svelte/field"
4import type { ComponentProps } from "svelte"
5
6type Orientation = "vertical" | "horizontal" | "responsive"
7
8interface Props extends ComponentProps<typeof FieldPrimitive.Root> {
9 orientation?: Orientation
10}
11
12let { class: className, orientation = "vertical", children, ...rest }: Props = $props()
13
14const orientationClasses: Record<Orientation, string> = {
15 vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
16 horizontal:
17 "has-[>[data-slot=field-content]]:&>[role=checkbox],[role=radio]]:mt-px flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto",
18 responsive:
19 "@md/field-group:has-[>[data-slot=field-content]]:&>[role=checkbox],[role=radio]]:mt-px flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @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",
20}
21</script>
22
23<FieldPrimitive.Root
24 data-slot="field"
25 data-orientation={orientation}
26 class={cn(
27 "group/field flex w-full gap-2 data-[invalid=true]:text-destructive",
28 orientationClasses[orientation],
29 className
30 )}
31 {...rest}
32>
33 {@render children?.()}
34</FieldPrimitive.Root>
351export { default as Field } from "./field.svelte";2export { default as FieldLabel } from "./field-label.svelte";3export { default as FieldDescription } from "./field-description.svelte";4export { default as FieldError } from "./field-error.svelte";5export { default as FieldGroup } from "./field-group.svelte";6export { default as FieldLegend } from "./field-legend.svelte";7export { default as FieldSeparator } from "./field-separator.svelte";8export { default as FieldSet } from "./field-set.svelte";9export { default as FieldContent } from "./field-content.svelte";10export { default as FieldTitle } from "./field-title.svelte";11export { default as FieldRequiredIndicator } from "./field-required-indicator.svelte";12Update the import paths to match your project setup.
Usage
1import {
2 FieldSet,
3 FieldLegend,
4 FieldGroup,
5 Field,
6 FieldContent,
7 FieldLabel,
8 FieldTitle,
9 FieldDescription,
10 FieldSeparator,
11 FieldError
12} from "@/components/ui/field"1<FieldSet>
2 <FieldLegend>Profile</FieldLegend>
3 <FieldDescription>This appears on invoices and emails.</FieldDescription>
4 <FieldGroup>
5 <Field>
6 <FieldLabel for="name">Full name</FieldLabel>
7 <Input id="name" autoComplete="off" placeholder="Evil Rabbit" />
8 <FieldDescription>This appears on invoices and emails.</FieldDescription>
9 </Field>
10 <Field>
11 <FieldLabel for="username">Username</FieldLabel>
12 <Input id="username" autoComplete="off" aria-invalid />
13 <FieldError>Choose another username.</FieldError>
14 </Field>
15 <Field orientation="horizontal">
16 <Switch id="newsletter" />
17 <FieldLabel for="newsletter">Subscribe to the newsletter</FieldLabel>
18 </Field>
19 </FieldGroup>
20</FieldSet>Examples
Checkbox
1<script module lang="ts">
2 import { Checkbox } from "@/components/ui/checkbox";
3 import {
4 Field,
Field Group
1<script module lang="ts">
2 import { Checkbox } from "@/components/ui/checkbox"
3 import {
4 Field,
Fieldset
1<script module lang="ts">
2 import {
3 Field,
4 FieldDescription,
Input
1<script module lang="ts">
2 import {
3 Field,
4 FieldDescription,
Textarea
1<script module lang="ts">
2 import {
3 Field,
4 FieldDescription,
Get PRO
Need premium blocks and templates? Upgrade to PRO and get access.