Presets

Blocks

Get PRO

Need premium blocks and templates? Upgrade to PRO and get access.

Upgrade
JoinLogin
Presets
Monochrome
Overview
  • Introduction
  • Components
  • Installation
Components
  • Accordion
  • Alert
  • Alert Dialog
  • Aspect Ratio
  • Avatar
  • Badge
  • Breadcrumb
  • Button
  • Button Group
  • CalendarNEW
  • Card
  • CarouselNEW
  • Checkbox
  • Collapsible
  • Dialog
  • Dropdown Menu
  • Empty
  • Field
  • Input
  • Input Group
  • Item
  • Kbd
  • Label
  • Native Select
  • Scroll Area
  • SelectNEW
  • Separator
  • SwitchNEW
  • Table
  • Tabs
  • Textarea
  • TooltipNEW

Field

PreviousNext

Combine labels, controls, and help text to compose accessible form fields and grouped inputs.

Installation

pnpm dlx sprawlify@latest add field
npx sprawlify@latest add field
yarn sprawlify@latest add field
bunx --bun sprawlify@latest add field

Install the following dependencies:

pnpm add @sprawlify/primitives @sprawlify/react
npm install @sprawlify/primitives @sprawlify/react
yarn add @sprawlify/primitives @sprawlify/react
bun add @sprawlify/primitives @sprawlify/react

Add the following files to your project:

field.tsx
1"use client";23import * as React from "react";4import { useMemo } from "react";5import { cn } from "@/lib/utils";6import { Separator } from "@/components/ui/separator";7import { sprawlify } from "@sprawlify/react";8import { Field as FieldPrimitive } from "@sprawlify/react/field";9import { Fieldset as FieldsetPrimitive } from "@sprawlify/react/fieldset";10import { cva, type VariantProps } from "class-variance-authority";1112function FieldSet({ className, ...props }: React.ComponentProps<typeof FieldsetPrimitive.Root>) {13  return (14    <FieldsetPrimitive.Root15      data-slot="field-set"16      className={cn(17        "flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",18        className,19      )}20      {...props}21    />22  );23}2425function FieldLegend({26  className,27  variant = "legend",28  ...props29}: React.ComponentProps<typeof FieldsetPrimitive.Legend> & {30  variant?: "legend" | "label";31}) {32  return (33    <FieldsetPrimitive.Legend34      data-slot="field-legend"35      data-variant={variant}36      className={cn(37        "mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",38        className,39      )}40      {...props}41    />42  );43}4445function FieldGroup({ className, ...props }: React.ComponentProps<typeof sprawlify.div>) {46  return (47    <sprawlify.div48      data-slot="field-group"49      className={cn(50        "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",51        className,52      )}53      {...props}54    />55  );56}5758const fieldVariants = cva("group/field flex w-full gap-2 data-[invalid=true]:text-destructive", {59  variants: {60    orientation: {61      vertical: "flex-col *:w-full [&>.sr-only]:w-auto",62      horizontal:63        "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",64      responsive:65        "@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",66    },67  },68  defaultVariants: {69    orientation: "vertical",70  },71});7273function Field({74  className,75  orientation = "vertical",76  ...props77}: React.ComponentProps<typeof FieldPrimitive.Root> & VariantProps<typeof fieldVariants>) {78  return (79    <FieldPrimitive.Root80      data-slot="field"81      data-orientation={orientation}82      className={cn(fieldVariants({ orientation }), className)}83      {...props}84    />85  );86}8788function FieldContent({ className, ...props }: React.ComponentProps<typeof sprawlify.div>) {89  return (90    <sprawlify.div91      data-scope="field"92      data-part="content"93      data-slot="field-content"94      className={cn("group/field-content flex flex-1 flex-col gap-0.5 leading-snug", className)}95      {...props}96    />97  );98}99100function FieldLabel({ className, ...props }: React.ComponentProps<typeof FieldPrimitive.Label>) {101  return (102    <FieldPrimitive.Label103      data-slot="field-label"104      className={cn(105        "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",106        "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",107        className,108      )}109      {...props}110    />111  );112}113114function FieldTitle({ className, ...props }: React.ComponentProps<typeof sprawlify.div>) {115  return (116    <sprawlify.div117      data-scope="field"118      data-part="title"119      data-slot="field-title"120      className={cn(121        "flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50",122        className,123      )}124      {...props}125    />126  );127}128129function FieldDescription({130  className,131  ...props132}: React.ComponentProps<typeof FieldPrimitive.HelperText>) {133  return (134    <FieldPrimitive.HelperText135      data-slot="field-description"136      className={cn(137        "text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",138        "last:mt-0 nth-last-2:-mt-1",139        "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",140        className,141      )}142      {...props}143    />144  );145}146147function FieldSeparator({148  children,149  className,150  ...props151}: React.ComponentProps<typeof sprawlify.div> & {152  children?: React.ReactNode;153}) {154  return (155    <sprawlify.div156      data-scope="field"157      data-part="separator"158      data-slot="field-separator"159      data-content={!!children}160      className={cn(161        "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",162        className,163      )}164      {...props}165    >166      <Separator className="absolute inset-0 top-1/2" />167      {children && (168        <sprawlify.span169          data-scope="field"170          data-part="separator-content"171          className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"172          data-slot="field-separator-content"173        >174          {children}175        </sprawlify.span>176      )}177    </sprawlify.div>178  );179}180181function FieldError({182  className,183  children,184  errors,185  ...props186}: React.ComponentProps<typeof FieldPrimitive.ErrorText> & {187  errors?: Array<{ message?: string } | undefined>;188}) {189  const content = useMemo(() => {190    if (children) {191      return children;192    }193194    if (!errors?.length) {195      return null;196    }197198    const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];199200    if (uniqueErrors?.length == 1) {201      return uniqueErrors[0]?.message;202    }203204    return (205      <sprawlify.ul className="ml-4 flex list-disc flex-col gap-1">206        {uniqueErrors.map(207          (error, index) =>208            error?.message && <sprawlify.li key={index}>{error.message}</sprawlify.li>,209        )}210      </sprawlify.ul>211    );212  }, [children, errors]);213214  if (!content) {215    return null;216  }217218  return (219    <FieldPrimitive.ErrorText220      role="alert"221      data-scope="field"222      data-part="error"223      data-slot="field-error"224      className={cn("text-sm font-normal text-destructive", className)}225      {...props}226    >227      {content}228    </FieldPrimitive.ErrorText>229  );230}231232function FieldRequiredIndicator({233  className,234  ...props235}: React.ComponentProps<typeof FieldPrimitive.RequiredIndicator>) {236  return (237    <FieldPrimitive.RequiredIndicator238      data-slot="field-required-indicator"239      className={cn("text-destructive", className)}240      {...props}241    />242  );243}244245export {246  Field,247  FieldLabel,248  FieldDescription,249  FieldError,250  FieldGroup,251  FieldLegend,252  FieldSeparator,253  FieldSet,254  FieldContent,255  FieldTitle,256  FieldRequiredIndicator,257};258

Update 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 htmlFor="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 htmlFor="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 htmlFor="newsletter">Subscribe to the newsletter</FieldLabel>
18    </Field>
19  </FieldGroup>
20</FieldSet>

Examples

Checkbox

1import { Checkbox } from "@/components/ui/checkbox";2import {3  Field,4  FieldContent,

Field Group

1import { Checkbox } from "@/components/ui/checkbox";2import {3  Field,4  FieldDescription,

Fieldset

1import {2  Field,3  FieldDescription,4  FieldGroup,

Input

1import { Field, FieldDescription, FieldGroup, FieldLabel, FieldSet } from "@/components/ui/field";2import { Input } from "@/components/ui/input";34export default function Preview() {

Textarea

1import { Field, FieldDescription, FieldGroup, FieldLabel, FieldSet } from "@/components/ui/field";2import { Textarea } from "@/components/ui/textarea";34export default function Preview() {

On This Page

InstallationUsageExamplesCheckboxField GroupFieldsetInputTextarea

Get PRO

Need premium blocks and templates? Upgrade to PRO and get access.