---
title: Field
description: "Combine labels, controls, and help text to compose accessible form fields."
---

```tsx
import * as stylex from '@stylexjs/stylex';

import {
  Field,
  FieldDescription,
  FieldGroup,
  FieldLabel,
  FieldLegend,
  FieldSeparator,
  FieldSet,
} from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import { container } from '@/lib/constants.stylex';

export default function FieldDemo() {
  return (
    <FieldSet style={styles.root}>
      <FieldLegend>Profile</FieldLegend>
      <FieldGroup>
        <Field>
          <FieldLabel htmlFor="field-name">Name</FieldLabel>
          <Input id="field-name" placeholder="Evil Rabbit" />
          <FieldDescription>Shown on your public profile.</FieldDescription>
        </Field>
        <FieldSeparator>Contact</FieldSeparator>
        <Field>
          <FieldLabel htmlFor="field-email">Email</FieldLabel>
          <Input id="field-email" type="email" placeholder="you@example.com" />
        </Field>
      </FieldGroup>
    </FieldSet>
  );
}

const styles = stylex.create({
  root: {
    width: container.sm,
  },
});
```

## Install

```bash
npx shadcn@latest add @madeui/field
```

## Usage

```tsx
import {
  Field,
  FieldDescription,
  FieldError,
  FieldGroup,
  FieldLabel,
  FieldLegend,
  FieldSeparator,
  FieldSet,
} from '@/components/ui/field';
```

## Composition

```tsx
<FieldSet>
  <FieldLegend />
  <FieldGroup>
    <Field>
      <FieldLabel />
      <Input />
      <FieldDescription />
      <FieldError />
    </Field>
    <FieldSeparator />
  </FieldGroup>
</FieldSet>
```

## Validation

Give the field a `name` and place a Base UI control inside — the control
joins the field automatically (label association, `aria-describedby`,
`aria-invalid`, red label on error). Combine with [Form](/docs/components/form)
to validate on submit, or set `validationMode="onChange"` per field. Custom
rules go in `validate`:

```tsx
<Field
  name="username"
  validate={(value) => (String(value).length < 2 ? 'Too short.' : null)}
>
  <FieldLabel>Username</FieldLabel>
  <Input required />
  <FieldError />
</Field>
```

Using react-hook-form or TanStack Form instead? See the
[Form page](/docs/components/form) — `Field` takes `invalid` and `FieldError`
takes `errors` for externally controlled state.

## API reference

Built on [Base UI Field](https://base-ui.com/react/components/field) and [Fieldset](https://base-ui.com/react/components/fieldset): `Field` renders `Field.Root` (all validation props forwarded: `name`, `validate`, `validationMode`, `invalid`, `disabled`, …), `FieldLabel`/`FieldDescription`/`FieldError` render the matching parts, `FieldSet`/`FieldLegend` render Fieldset parts. `FieldGroup`, `FieldContent`, `FieldTitle`, and `FieldSeparator` are layout markup.

### Field

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` |  |
| `name` | `string` | — | Identifies the field in form values and server errors. |
| `validate` | `(value, formValues) => string \| string[] \| null` | — | Custom validation; return message(s) or null. |
| `invalid` | `boolean` | — | Force the invalid state — for external form libraries. |

### FieldError

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `errors` | `Array<{ message?: string }>` | — | External errors rendered directly (de-duplicated; one message as text, several as a list; nothing when empty). Without it, the surrounding field's own validation message renders. |
| `match` | `boolean \| keyof ValidityState` | — | When to show `children` (Base UI). `true` always shows — for external control. |

`FieldLegend` accepts `variant`: `'legend'` (base size) or `'label'` (sm). `FieldSeparator` accepts children rendered centered over the line. All parts accept a `style` prop (`StyleXStyles`, merged last).
