Skip to content
SevenUI
Esc
navigateopen⌘Jpreview
Sign in
On this page

Field

Associates a label, control, description, and validation error for a form field.

import {
  Field,
  FieldDescription,
  FieldLabel,
} from "@/registry/base/ui/field";
import { Input } from "@/registry/base/ui/input";

export default function FieldDemo() {
  return (
    <div className="w-full max-w-sm">
      <Field name="email">
        <FieldLabel>Email</FieldLabel>
        <Input required type="email" placeholder="name@example.com" />
        <FieldDescription>Used to send you order updates.</FieldDescription>
      </Field>
    </div>
  );
}

Installation

npx shadcn@latest add https://sevenui.dev/r/field.json

Usage

import {
  Field,
  FieldDescription,
  FieldError,
  FieldLabel,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";

<Field name="email">
  <FieldLabel>Email</FieldLabel>
  <Input required type="email" placeholder="name@example.com" />
  <FieldDescription>Used to send you order updates.</FieldDescription>
  <FieldError />
</Field>;

Field wires everything together for you: htmlFor/id between label and control, aria-describedby for the description and error, and aria-invalid on the control are all managed by the underlying Base UI Field context — no manual ids. Every SevenUI form control (Input, Textarea, Checkbox, Radio Group, Select, Switch, Slider, Number Field, …) participates automatically.

Examples

Orientation

horizontal places the label and control on one row — pair it with FieldContent to stack a label and description beside the control. responsive stacks in narrow containers and switches to the horizontal layout from the @md container breakpoint; it requires a FieldGroup ancestor, which provides the container query scope.

import {
  Field,
  FieldContent,
  FieldDescription,
  FieldGroup,
  FieldLabel,
} from "@/registry/base/ui/field";
import { Input } from "@/registry/base/ui/input";
import { Switch } from "@/registry/base/ui/switch";

export default function FieldOrientation() {
  return (
    <FieldGroup className="max-w-md">
      <Field orientation="horizontal" name="marketing">
        <FieldContent>
          <FieldLabel>Marketing emails</FieldLabel>
          <FieldDescription>Occasional product news. No spam.</FieldDescription>
        </FieldContent>
        <Switch />
      </Field>
      <Field orientation="responsive" name="displayName">
        <FieldContent>
          <FieldLabel>Display name</FieldLabel>
          <FieldDescription>
            Stacks on narrow containers, inline on wide ones.
          </FieldDescription>
        </FieldContent>
        <Input placeholder="Margaret Hamilton" />
      </Field>
    </FieldGroup>
  );
}

Validation

A bare <FieldError /> renders the field’s active error on its own — native constraint messages (required, type="email", …), messages returned by validate, or server errors passed through Form. This example validates on blur with a custom validate function.

import {
  Field,
  FieldDescription,
  FieldError,
  FieldLabel,
} from "@/registry/base/ui/field";
import { Input } from "@/registry/base/ui/input";

export default function FieldValidation() {
  return (
    <div className="w-full max-w-sm">
      <Field
        name="username"
        validationMode="onBlur"
        validate={(value) =>
          String(value ?? "").length >= 3
            ? null
            : "Username must be at least 3 characters."
        }
      >
        <FieldLabel>Username</FieldLabel>
        <Input required placeholder="sevenui" />
        <FieldDescription>
          Validates when the input loses focus.
        </FieldDescription>
        <FieldError />
      </Field>
    </div>
  );
}

To scope a literal message to one native validity state, use match:

<FieldError match="valueMissing">Please enter your email.</FieldError>
<FieldError match="typeMismatch">Not a valid email address.</FieldError>

React Hook Form

Any form library can drive a field through three generic props — invalid, touched, and dirty — plus the errors array on FieldError. With react-hook-form, spread fieldState from a Controller:

"use client";

import { Controller, useForm } from "react-hook-form";

import { Button } from "@/registry/base/ui/button";
import {
  Field,
  FieldDescription,
  FieldError,
  FieldLabel,
} from "@/registry/base/ui/field";
import { Input } from "@/registry/base/ui/input";

type FormValues = { username: string };

export default function FieldRhf() {
  const form = useForm<FormValues>({
    defaultValues: { username: "" },
  });

  function onSubmit(values: FormValues) {
    console.log(values);
  }

  return (
    <form
      onSubmit={form.handleSubmit(onSubmit)}
      className="grid w-full max-w-sm gap-4"
      noValidate
    >
      <Controller
        control={form.control}
        name="username"
        rules={{
          required: "Username is required.",
          minLength: {
            value: 3,
            message: "Username must be at least 3 characters.",
          },
        }}
        render={({ field, fieldState }) => (
          <Field
            name={field.name}
            invalid={fieldState.invalid}
            touched={fieldState.isTouched}
            dirty={fieldState.isDirty}
          >
            <FieldLabel>Username</FieldLabel>
            <Input placeholder="sevenui" {...field} />
            <FieldDescription>
              This is your public display name.
            </FieldDescription>
            <FieldError
              errors={fieldState.error ? [fieldState.error] : undefined}
            />
          </Field>
        )}
      />
      <Button type="submit">Submit</Button>
    </form>
  );
}

The same pattern works for TanStack Form (field.state.meta.isTouched, field.state.meta.errors) or any validator implementing Standard Schema — the component has no form-library imports.

API reference

Field

Extends the Base UI Field Root — all its props apply.

Prop Type Default
orientation "vertical" | "horizontal" | "responsive" "vertical"
name string — keys the field for form submission and Form errors
validate (value, formValues) => string | string[] | null | Promise<...>
validationMode "onSubmit" | "onBlur" | "onChange" "onSubmit"
invalid / touched / dirty boolean — for external form libraries
disabled boolean — supersedes the control’s own disabled false

Every part carries the field’s state as data attributes for styling: data-invalid, data-valid, data-dirty, data-touched, data-filled, data-focused, data-disabled.

FieldLabel

Extends the Base UI Field Label — associated with the field’s control automatically, no htmlFor needed. Renders through Label, so label styling applies. Wrapping a nested Field turns it into a selectable card (checkbox/radio card patterns).

FieldDescription

Extends the Base UI Field Description — linked to the control via aria-describedby automatically.

FieldError

Extends the Base UI Field Error. Visible only while the field is invalid, unless external content forces it.

Prop Type Default
errors Array<{ message?: string } | undefined>
match boolean | keyof ValidityState

With no props it auto-renders the field’s active error from context. errors accepts issues from any form library or Standard Schema validator (Zod, Valibot, ArkType); messages are deduped, and multiple messages render as a list. match="valueMissing" (and other ValidityState keys) scopes literal children to one native validity condition.

Layout parts

Plain styled elements with no form wiring — compose freely around any number of fields:

Component Description
FieldSet <fieldset> grouping related fields.
FieldLegend <legend>; variant: "legend" (default) or "label".
FieldGroup Vertical stack of fields; container-query scope for responsive.
FieldContent Column wrapper pairing a label/description beside a control.
FieldTitle Label-styled heading for non-label contexts.
FieldSeparator Divider between fields, with optional inline content.

Was this page helpful?