---
title: "Form fields"
description: "Labelled fields with descriptions, errors and widths: TextField, EmailField, PhoneField, PasswordField and seven more."
requested_language: ar
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/ar/components/form-fields
version: unreleased (main)
docs_index: https://docs.systhema.app/ar/llms.txt
---
> This page isn't translated yet. Showing English.


A field component renders a whole form row in one element: a [`FormGroup`](https://docs.systhema.app/ar/components/form.md#formgroup) with a label, an optional description, the control and an error message, with the ARIA attributes between them already wired. There is one field per control type. Start with these; drop to the [primitives](https://docs.systhema.app/ar/components/form-controls.md) only for layouts they don't cover.

```tsx preview title="Fields"
import { EmailField, Form, PasswordField, TextField } from '@systhemaui/next'

export default function Demo() {
  return (
    <Form style={{ width: '100%', maxWidth: 480 }}>
      <TextField name="fields-name" label="Full name" autoComplete="name" required />
      <EmailField name="fields-email" label="Email" description="We never share it." required />
      <PasswordField
        name="fields-password"
        label="Password"
        description="At least 12 characters."
        descriptionPosition="bottom"
        autoComplete="new-password"
        minLength={12}
        required
      />
    </Form>
  )
}
```

## Import

```tsx
import { EmailField, TextField } from '@systhemaui/next'
```

In a React app without Next.js, import them from `@systhemaui/react`.

## Shared props

Every field takes these props on top of its control's own attributes:

| Prop                  | What it does                                                                                                                 |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `name`                | Required. Also the default `id`, which the label's `htmlFor` points at                                                       |
| `label`               | The label. Any React node on the single-control fields; a string on `RadioField` and `CheckboxField`                         |
| `description`         | Help text in a `FormDescription`                                                                                             |
| `descriptionPosition` | `'top'` (default) puts the description between label and control, `'bottom'` below the control                               |
| `invalidMessage`      | The error text, in a `FormInvalidMessage`                                                                                    |
| `width`               | A CSS percentage that sizes the field inside a `FormRow` from `md` up (see [Widths](https://docs.systhema.app/ar/components/form.md#widths))                       |
| `slotProps`           | Props for the surrounding elements: `group`, `label`, `description`, `invalidMessage` (and `inputGroup` on the group fields) |

Everything else (`required`, `placeholder`, `defaultValue`, `onChange`, `className`, …) goes to the control itself.

## Examples

### Description position

```tsx preview title="Description above and below"
import { FormRow, TextField } from '@systhemaui/next'

export default function Demo() {
  return (
    <FormRow style={{ width: '100%', maxWidth: 640 }}>
      <TextField name="position-top" label="Username" description="Shown on your profile." />
      <TextField
        name="position-bottom"
        label="Display name"
        description="Shown on your profile."
        descriptionPosition="bottom"
      />
    </FormRow>
  )
}
```

### Showing an error

The error message stays hidden until the browser marks the control `:user-invalid`, which happens once the visitor has changed the value or tried to submit the form. So with native validation (`required`, `pattern`, `minLength`, the input type), pass `invalidMessage` up front and the message appears at the right moment. Type something that isn't an email address below and leave the field:

```tsx preview title="Native validation"
import { EmailField } from '@systhemaui/next'

export default function Demo() {
  return (
    <div style={{ width: '100%', maxWidth: 480 }}>
      <EmailField
        name="error-native"
        label="Email"
        required
        invalidMessage="Enter an email address like jane@example.com."
      />
    </div>
  )
}
```

When you validate in JavaScript (or on the server), add the `is-invalid` class to the control: it forces the invalid style and shows the message immediately.

```tsx preview title="Server-side error"
import { TextField } from '@systhemaui/next'

export default function Demo() {
  return (
    <div style={{ width: '100%', maxWidth: 480 }}>
      <TextField
        name="error-server"
        label="Voucher code"
        defaultValue="SUMMER2019"
        className="is-invalid"
        invalidMessage="This code expired on 31 August 2019."
      />
    </div>
  )
}
```

> [!NOTE]
> A field sets `aria-invalid="true"` on its control whenever `invalidMessage` is passed, even while the message is still hidden. For an accurate screen-reader state, pass `invalidMessage` only while the value is invalid.

### Widths

```tsx preview title="Widths in a row"
import { EmailField, FormRow, PhoneField, TextField } from '@systhemaui/next'

export default function Demo() {
  return (
    <FormRow style={{ width: '100%', maxWidth: 640 }}>
      <TextField name="widths-first" label="First name" width="50%" />
      <TextField name="widths-last" label="Last name" width="50%" />
      <EmailField name="widths-email" label="Email" width="67%" />
      <PhoneField name="widths-phone" label="Phone" width="33%" />
    </FormRow>
  )
}
```

### Slot props

`slotProps` reaches the elements around the control without dropping to primitives, for example to give the group an id or restyle the description. The control's own props stay at the top level:

```tsx
;<TextField
  name="company"
  label="Company"
  description="Optional"
  autoComplete="organization"
  slotProps={{
    group: { id: 'company-group' },
    description: { className: 'italic' },
  }}
/>
```

## Fields

### `TextField`

A text input. `type` defaults to `'text'`; override it for other text-like inputs such as `url` or `search`. For email, phone, number, date and password, use the dedicated field, which sets the right `inputMode` and `autoComplete`.

```tsx preview title="TextField"
import { TextField } from '@systhemaui/next'

export default function Demo() {
  return (
    <div style={{ width: '100%', maxWidth: 480 }}>
      <TextField
        name="field-website"
        type="url"
        label="Website"
        placeholder="https://"
        description="Include https://."
        invalidMessage="Enter a full URL."
      />
    </div>
  )
}
```

### `EmailField`

An `type="email"` input with `inputMode="email"`, `autoComplete="email"` and a case-insensitive `pattern` that requires a domain with a top-level part (`jane@example` fails, `Jane.Doe+news@Example.com` passes). Each default can be overridden.

```tsx preview title="EmailField"
import { EmailField } from '@systhemaui/next'

export default function Demo() {
  return (
    <div style={{ width: '100%', maxWidth: 480 }}>
      <EmailField
        name="field-email"
        label="Work email"
        defaultValue="jane@example"
        className="is-invalid"
        invalidMessage="Add the domain ending, for example .com."
      />
    </div>
  )
}
```

### `PhoneField`

A `type="tel"` input with `inputMode="tel"` and `autoComplete="tel"`. It adds no pattern, because phone formats vary; pass `pattern` if you need one.

```tsx preview title="PhoneField"
import { PhoneField } from '@systhemaui/next'

export default function Demo() {
  return (
    <div style={{ width: '100%', maxWidth: 480 }}>
      <PhoneField
        name="field-phone"
        label="Phone"
        placeholder="+32 470 12 34 56"
        description="Only used for delivery updates."
      />
    </div>
  )
}
```

### `PasswordField`

A `type="password"` input. Set `autoComplete` to `current-password` on sign-in forms and `new-password` on sign-up and reset forms, so password managers act correctly.

```tsx preview title="PasswordField"
import { PasswordField } from '@systhemaui/next'

export default function Demo() {
  return (
    <div style={{ width: '100%', maxWidth: 480 }}>
      <PasswordField
        name="field-password"
        label="New password"
        autoComplete="new-password"
        minLength={12}
        required
        description="At least 12 characters."
        descriptionPosition="bottom"
        invalidMessage="Use at least 12 characters."
      />
    </div>
  )
}
```

### `NumberField`

A `type="number"` input with `inputMode="decimal"`. Use `min`, `max` and `step` for the range.

```tsx preview title="NumberField"
import { NumberField } from '@systhemaui/next'

export default function Demo() {
  return (
    <div style={{ width: '100%', maxWidth: 480 }}>
      <NumberField
        name="field-guests"
        label="Guests"
        min={1}
        max={12}
        defaultValue={2}
        description="Up to 12 per booking."
        invalidMessage="Choose between 1 and 12 guests."
      />
    </div>
  )
}
```

### `DateField`

A date input. `inputType` picks `'date'` (default) or `'datetime-local'`; `min` and `max` take ISO strings.

```tsx preview title="DateField"
import { DateField, FormRow } from '@systhemaui/next'

export default function Demo() {
  return (
    <FormRow style={{ width: '100%', maxWidth: 640 }}>
      <DateField name="field-arrival" label="Arrival" min="2026-01-01" />
      <DateField name="field-meeting" label="Call" inputType="datetime-local" />
    </FormRow>
  )
}
```

### `TextareaField`

A multi-line text area. The default CSS gives it a fixed height of 328 px with resizing off; `resize` lets the visitor resize it vertically, and a `style` or class sets another height.

```tsx preview title="TextareaField"
import { TextareaField } from '@systhemaui/next'

export default function Demo() {
  return (
    <div style={{ width: '100%', maxWidth: 480 }}>
      <TextareaField
        name="field-message"
        label="Message"
        description="Tell us about your project."
        maxLength={2000}
        resize
        style={{ height: 160 }}
      />
    </div>
  )
}
```

### `SelectField`

A [`FormSelect`](https://docs.systhema.app/ar/components/form-select.md) with a label and messages. It takes the same `Option` and `SelectPlaceholder` children and the same `selectClassName`, `iconClassName`, `prepend`, `append` and `disableIcon` props. `className` goes on the select wrapper.

```tsx preview title="SelectField"
import { Option, SelectField, SelectPlaceholder } from '@systhemaui/next'

export default function Demo() {
  return (
    <div style={{ width: '100%', maxWidth: 480 }}>
      <SelectField
        name="field-topic"
        label="Topic"
        required
        description="Routes your message to the right team."
        invalidMessage="Choose a topic."
      >
        <SelectPlaceholder>Choose a topic</SelectPlaceholder>
        <Option value="sales">Sales</Option>
        <Option value="support">Support</Option>
        <Option value="press">Press</Option>
      </SelectField>
    </div>
  )
}
```

### `FileField`

A [`FormFileInput`](https://docs.systhema.app/ar/components/form-controls.md#formfileinput) with a label and messages; `accept` and `multiple` pass through.

```tsx preview title="FileField"
import { FileField } from '@systhemaui/next'

export default function Demo() {
  return (
    <div style={{ width: '100%', maxWidth: 480 }}>
      <FileField
        name="field-attachments"
        label="Attachments"
        accept="image/*,.pdf"
        multiple
        description="Images or PDF, up to 10 MB each."
      />
    </div>
  )
}
```

### `RadioField` and `CheckboxField`

The group fields take their choices as `items`, each an object with the [`FormRadio` or `FormCheckbox`](https://docs.systhema.app/ar/components/form-choice.md) props minus `name`. The group label is a `<div>` that names the group through `aria-labelledby`, and the description and error attach to the group. `required` is applied to every radio, so one choice is needed.

```tsx preview title="RadioField"
import { RadioField } from '@systhemaui/next'

export default function Demo() {
  return (
    <RadioField
      name="field-delivery"
      label="Delivery"
      description="Pick-up is free."
      required
      items={[
        {
          id: 'delivery-standard',
          value: 'standard',
          label: 'Standard (3 to 5 days)',
          defaultChecked: true,
        },
        { id: 'delivery-express', value: 'express', label: 'Express (next day)' },
        { id: 'delivery-pickup', value: 'pickup', label: 'Pick up in store' },
      ]}
    />
  )
}
```

Each item's `id` defaults to its `value`. Two groups on one page with the same values (`yes` / `no`) would then share ids, so give the items ids, as above.

For checkboxes, `requiredMode` decides what `required` means: `'each'` (default) puts `required` on every box, for a list of things that must all be confirmed; `'any'` asks for at least one box. In `'any'` mode the field reads the items' `checked` props, so control the items with state:

```tsx preview title="CheckboxField with requiredMode any"
'use client'

import { useState } from 'react'
import { CheckboxField } from '@systhemaui/next'

const topics = [
  { value: 'design', label: 'Design' },
  { value: 'development', label: 'Development' },
  { value: 'content', label: 'Content' },
]

export default function Demo() {
  const [selected, setSelected] = useState<string[]>(['design'])

  return (
    <CheckboxField
      name="field-topics"
      label="Topics"
      description="Choose at least one."
      required
      requiredMode="any"
      invalidMessage="Choose at least one topic."
      items={topics.map((topic) => ({
        ...topic,
        id: `topics-${topic.value}`,
        checked: selected.includes(topic.value),
        onChange: () =>
          setSelected((current) =>
            current.includes(topic.value)
              ? current.filter((value) => value !== topic.value)
              : [...current, topic.value],
          ),
      }))}
    />
  )
}
```

## Props

### `TextField`

<!-- generated:props @systhemaui/react TextFieldProps -->

| Prop                          | Type                     | Default  | Description                                                                                                                                                                                                                                                                                                                      |
| ----------------------------- | ------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                          | `string`                 | -        |                                                                                                                                                                                                                                                                                                                                  |
| `name` (required)             | `string`                 | -        |                                                                                                                                                                                                                                                                                                                                  |
| `autoComplete`                | `string`                 | -        |                                                                                                                                                                                                                                                                                                                                  |
| `label`                       | `ReactNode`              | -        |                                                                                                                                                                                                                                                                                                                                  |
| `description`                 | `ReactNode`              | -        |                                                                                                                                                                                                                                                                                                                                  |
| `descriptionPosition`         | `'top' \| 'bottom'`      | -        |                                                                                                                                                                                                                                                                                                                                  |
| `invalidMessage`              | `ReactNode`              | -        |                                                                                                                                                                                                                                                                                                                                  |
| `type`                        | `HTMLInputTypeAttribute` | `'text'` | Defaults to `'text'`. Override for niche text-style inputs (e.g. `'search'`, `'url'`). For `email`, `tel`, `number`, `date`, `password`, prefer the dedicated `EmailField` / `PhoneField` / `NumberField` / `DateField` / `PasswordField` — those carry sensible default `inputMode`, `autoComplete`, and (for email) `pattern`. |
| `width`                       | `string`                 | -        |                                                                                                                                                                                                                                                                                                                                  |
| `slotProps`                   | `FieldSlotProps`         | -        |                                                                                                                                                                                                                                                                                                                                  |
| …and all `<input>` attributes |                          |          |                                                                                                                                                                                                                                                                                                                                  |

<!-- /generated -->

### `EmailField`

<!-- generated:props @systhemaui/react EmailFieldProps -->

| Prop                               | Type                | Default | Description |
| ---------------------------------- | ------------------- | ------- | ----------- |
| `id`                               | `string`            | -       |             |
| `name` (required)                  | `string`            | -       |             |
| `autoComplete`                     | `string`            | -       |             |
| `label`                            | `ReactNode`         | -       |             |
| `description`                      | `ReactNode`         | -       |             |
| `descriptionPosition`              | `'top' \| 'bottom'` | -       |             |
| `invalidMessage`                   | `ReactNode`         | -       |             |
| `width`                            | `string`            | -       |             |
| `slotProps`                        | `FieldSlotProps`    | -       |             |
| …and all inherited HTML attributes |                     |         |             |

<!-- /generated -->

### `PhoneField`

<!-- generated:props @systhemaui/react PhoneFieldProps -->

| Prop                               | Type                | Default | Description |
| ---------------------------------- | ------------------- | ------- | ----------- |
| `id`                               | `string`            | -       |             |
| `name` (required)                  | `string`            | -       |             |
| `autoComplete`                     | `string`            | -       |             |
| `label`                            | `ReactNode`         | -       |             |
| `description`                      | `ReactNode`         | -       |             |
| `descriptionPosition`              | `'top' \| 'bottom'` | -       |             |
| `invalidMessage`                   | `ReactNode`         | -       |             |
| `width`                            | `string`            | -       |             |
| `slotProps`                        | `FieldSlotProps`    | -       |             |
| …and all inherited HTML attributes |                     |         |             |

<!-- /generated -->

### `PasswordField`

<!-- generated:props @systhemaui/react PasswordFieldProps -->

| Prop                               | Type                | Default | Description |
| ---------------------------------- | ------------------- | ------- | ----------- |
| `id`                               | `string`            | -       |             |
| `name` (required)                  | `string`            | -       |             |
| `autoComplete`                     | `string`            | -       |             |
| `label`                            | `ReactNode`         | -       |             |
| `description`                      | `ReactNode`         | -       |             |
| `descriptionPosition`              | `'top' \| 'bottom'` | -       |             |
| `invalidMessage`                   | `ReactNode`         | -       |             |
| `width`                            | `string`            | -       |             |
| `slotProps`                        | `FieldSlotProps`    | -       |             |
| …and all inherited HTML attributes |                     |         |             |

<!-- /generated -->

### `NumberField`

<!-- generated:props @systhemaui/react NumberFieldProps -->

| Prop                               | Type                | Default | Description |
| ---------------------------------- | ------------------- | ------- | ----------- |
| `id`                               | `string`            | -       |             |
| `name` (required)                  | `string`            | -       |             |
| `autoComplete`                     | `string`            | -       |             |
| `label`                            | `ReactNode`         | -       |             |
| `description`                      | `ReactNode`         | -       |             |
| `descriptionPosition`              | `'top' \| 'bottom'` | -       |             |
| `invalidMessage`                   | `ReactNode`         | -       |             |
| `width`                            | `string`            | -       |             |
| `slotProps`                        | `FieldSlotProps`    | -       |             |
| …and all inherited HTML attributes |                     |         |             |

<!-- /generated -->

### `DateField`

<!-- generated:props @systhemaui/react DateFieldProps -->

| Prop                               | Type                         | Default  | Description |
| ---------------------------------- | ---------------------------- | -------- | ----------- |
| `id`                               | `string`                     | -        |             |
| `name` (required)                  | `string`                     | -        |             |
| `autoComplete`                     | `string`                     | -        |             |
| `label`                            | `ReactNode`                  | -        |             |
| `description`                      | `ReactNode`                  | -        |             |
| `descriptionPosition`              | `'top' \| 'bottom'`          | -        |             |
| `invalidMessage`                   | `ReactNode`                  | -        |             |
| `inputType`                        | `'date' \| 'datetime-local'` | `'date'` |             |
| `width`                            | `string`                     | -        |             |
| `slotProps`                        | `FieldSlotProps`             | -        |             |
| …and all inherited HTML attributes |                              |          |             |

<!-- /generated -->

### `TextareaField`

<!-- generated:props @systhemaui/react TextareaFieldProps -->

| Prop                               | Type                | Default | Description |
| ---------------------------------- | ------------------- | ------- | ----------- |
| `name` (required)                  | `string`            | -       |             |
| `label`                            | `ReactNode`         | -       |             |
| `description`                      | `ReactNode`         | -       |             |
| `descriptionPosition`              | `'top' \| 'bottom'` | -       |             |
| `invalidMessage`                   | `ReactNode`         | -       |             |
| `id`                               | `string`            | -       |             |
| `resize`                           | `boolean`           | `false` |             |
| `width`                            | `string`            | -       |             |
| `slotProps`                        | `FieldSlotProps`    | -       |             |
| …and all inherited HTML attributes |                     |         |             |

<!-- /generated -->

### `SelectField`

<!-- generated:props @systhemaui/react SelectFieldProps -->

| Prop                               | Type                | Default | Description |
| ---------------------------------- | ------------------- | ------- | ----------- |
| `children` (required)              | `ReactNode`         | -       |             |
| `name` (required)                  | `string`            | -       |             |
| `prepend`                          | `ReactNode`         | -       |             |
| `append`                           | `ReactNode`         | -       |             |
| `selectClassName`                  | `string`            | -       |             |
| `iconClassName`                    | `string`            | -       |             |
| `disableIcon`                      | `boolean`           | -       |             |
| `label`                            | `ReactNode`         | -       |             |
| `description`                      | `ReactNode`         | -       |             |
| `descriptionPosition`              | `'top' \| 'bottom'` | -       |             |
| `invalidMessage`                   | `ReactNode`         | -       |             |
| `id`                               | `string`            | -       |             |
| `className`                        | `string`            | -       |             |
| `width`                            | `string`            | -       |             |
| `slotProps`                        | `FieldSlotProps`    | -       |             |
| …and all inherited HTML attributes |                     |         |             |

<!-- /generated -->

### `FileField`

<!-- generated:props @systhemaui/react FileFieldProps -->

| Prop                               | Type                | Default | Description |
| ---------------------------------- | ------------------- | ------- | ----------- |
| `id`                               | `string`            | -       |             |
| `name` (required)                  | `string`            | -       |             |
| `accept`                           | `string`            | -       |             |
| `multiple`                         | `boolean`           | -       |             |
| `label`                            | `ReactNode`         | -       |             |
| `description`                      | `ReactNode`         | -       |             |
| `descriptionPosition`              | `'top' \| 'bottom'` | -       |             |
| `invalidMessage`                   | `ReactNode`         | -       |             |
| `width`                            | `string`            | -       |             |
| `slotProps`                        | `FieldSlotProps`    | -       |             |
| …and all inherited HTML attributes |                     |         |             |

<!-- /generated -->

### `RadioField`

<!-- generated:props @systhemaui/react RadioFieldProps -->

| Prop                  | Type                             | Default | Description |
| --------------------- | -------------------------------- | ------- | ----------- |
| `name` (required)     | `string`                         | -       |             |
| `items` (required)    | `Omit<FormRadioProps, 'name'>[]` | -       |             |
| `label`               | `string`                         | -       |             |
| `id`                  | `string`                         | -       |             |
| `required`            | `boolean`                        | -       |             |
| `description`         | `ReactNode`                      | -       |             |
| `descriptionPosition` | `'top' \| 'bottom'`              | -       |             |
| `invalidMessage`      | `ReactNode`                      | -       |             |
| `width`               | `string`                         | -       |             |
| `slotProps`           | `InlineFieldSlotProps`           | -       |             |

<!-- /generated -->

### `CheckboxField`

<!-- generated:props @systhemaui/react CheckboxFieldProps -->

| Prop                  | Type                                | Default  | Description                                                                                                                                                                                                                         |
| --------------------- | ----------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` (required)     | `string`                            | -        |                                                                                                                                                                                                                                     |
| `items` (required)    | `Omit<FormCheckboxProps, 'name'>[]` | -        |                                                                                                                                                                                                                                     |
| `label`               | `string`                            | -        |                                                                                                                                                                                                                                     |
| `id`                  | `string`                            | -        |                                                                                                                                                                                                                                     |
| `required`            | `boolean`                           | -        |                                                                                                                                                                                                                                     |
| `requiredMode`        | `'each' \| 'any'`                   | `"each"` | How required validation works for checkbox groups: - "each": every checkbox must be checked (native HTML `required` on each input) - "any": at least one checkbox must be checked (uses a hidden proxy input for native validation) |
| `description`         | `ReactNode`                         | -        |                                                                                                                                                                                                                                     |
| `descriptionPosition` | `'top' \| 'bottom'`                 | -        |                                                                                                                                                                                                                                     |
| `invalidMessage`      | `ReactNode`                         | -        |                                                                                                                                                                                                                                     |
| `width`               | `string`                            | -        |                                                                                                                                                                                                                                     |
| `slotProps`           | `InlineFieldSlotProps`              | -        |                                                                                                                                                                                                                                     |

<!-- /generated -->

### `FieldSlotProps`

<!-- generated:props @systhemaui/react FieldSlotProps -->

| Prop             | Type                                                                                                       | Default | Description |
| ---------------- | ---------------------------------------------------------------------------------------------------------- | ------- | ----------- |
| `group`          | `Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, 'ref'>`                           | -       |             |
| `label`          | `Omit<Omit<DetailedHTMLProps<LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>, 'ref'>, 'htmlFor'>` | -       |             |
| `description`    | `Omit<DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>, 'ref'>`               | -       |             |
| `invalidMessage` | `Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, 'ref'>`                           | -       |             |

<!-- /generated -->

### `InlineFieldSlotProps`

<!-- generated:props @systhemaui/react InlineFieldSlotProps -->

| Prop             | Type                                                                                                       | Default | Description |
| ---------------- | ---------------------------------------------------------------------------------------------------------- | ------- | ----------- |
| `group`          | `Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, 'ref'>`                           | -       |             |
| `label`          | `Omit<Omit<DetailedHTMLProps<LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>, 'ref'>, 'htmlFor'>` | -       |             |
| `description`    | `Omit<DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>, 'ref'>`               | -       |             |
| `invalidMessage` | `Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, 'ref'>`                           | -       |             |
| `inputGroup`     | `Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, 'ref'>`                           | -       |             |

<!-- /generated -->

## HTML and CSS

A field renders this markup. Write it by hand in an HTML project; the ids and `aria-*` attributes are what the component adds for you.

```html
<div class="form-group" style="--width: 50%">
  <label class="form-label" for="email">Email</label>
  <p class="form-description" id="email-description">We never share it.</p>
  <input
    class="form-input"
    id="email"
    name="email"
    type="email"
    inputmode="email"
    autocomplete="email"
    required
    aria-describedby="email-description email-error"
  />
  <div class="form-invalid-message" id="email-error" role="alert">Enter a valid email.</div>
</div>

<div class="form-group">
  <div class="form-label" id="delivery-label">Delivery</div>
  <div class="form-inline-input-group" role="radiogroup" aria-labelledby="delivery-label">
    <div class="form-radio">
      <input type="radio" id="standard" name="delivery" value="standard" required />
      <label for="standard">Standard</label>
    </div>
    <div class="form-radio">
      <input type="radio" id="express" name="delivery" value="express" required />
      <label for="express">Express</label>
    </div>
  </div>
</div>
```

<!-- generated:utilities form -->

| Class                     | Styles                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `form`                    | `& > :not([hidden], input[type="hidden"]) ~ :not([hidden], input[type="hidden"]) { margin-top: var(--form-row-gap-y); }`                                                                                                                                                                                                                                                                                                                                                                                                        |
| `form-row`                | `@media (width < 640px) { & { display: flex; flex-direction: column; row-gap: var(--form-row-gap-y); } }`<br>`@media (width >= 640px) { & { display: flex; flex-wrap: wrap; gap: var(--form-row-gap-x); } }`<br>`@media (width >= 640px) { & > .form-group { flex: 1 1 0%; min-width: 0; } }`<br>`@media (width >= 640px) { & > .form-group[style*="--width"] { flex: 0 0 auto; width: calc(var(--width) - var(--form-row-gap-x) * (1 - var(--width) / 100%)); } }`                                                             |
| `form-group`              | `@media (width >= 640px) { .form-row > .form-group { flex: 1 1 0%; min-width: 0; } }`<br>`@media (width >= 640px) { .form-row > .form-group[style*="--width"] { flex: 0 0 auto; width: calc(var(--width) - var(--form-row-gap-x) * (1 - var(--width) / 100%)); } }`<br>`& > :not([hidden]) ~ :not([hidden]) { margin-top: var(--form-group-gap-y); }`<br>`&:has(:required) .form-label::after { content: ' *'; color: var(--color-form-label-required); }`<br>+ 1 more rule                                                     |
| `form-label`              | `& { display: block; color: var(--color-form-label); }`<br>`& { font-family: var(--font-form-label-font-family); font-size: var(--typography-form-label-font-size); font-weight: var(--font-form-label-font-weight); letter-spacing: var(--typography-form-label-letter-spacing); line-height: var(--typography-form-label-line-height); text-transform: none; text-decoration: none; font-style: var(--font-form-label-font-style); }`<br>+ 1 more rule                                                                        |
| `form-description`        | `& { display: block; color: var(--color-form-description); }`<br>`& { font-family: var(--font-form-description-font-family); font-size: var(--typography-form-description-font-size); font-weight: var(--font-form-description-font-weight); letter-spacing: var(--typography-form-description-letter-spacing); line-height: var(--typography-form-description-line-height); text-transform: none; text-decoration: none; font-style: var(--font-form-description-font-style); }`                                               |
| `form-input`              | `& { --tw-shadow-color: var(--color-form-input-block-default-shadow); --tw-shadow: var(--tw-shadow-color); width: 100%; outline: none; border-style: solid; padding-inline: var(--form-input-block-padding-x); padding-block: var(--form-input-block-padding-y); border-radius: var(--form-input-block-border-radius); border-width: var(--form-input-block-border-width); background-color: var(--color-form-input-block-default-background); border-color: var(--color-form-input-block-defaul…`<br>+ 4 more rules            |
| `form-select`             | `& { --tw-shadow-color: var(--color-form-input-block-default-shadow); --tw-shadow: var(--tw-shadow-color); width: 100%; outline: none; border-style: solid; padding-inline: var(--form-input-block-padding-x); padding-block: var(--form-input-block-padding-y); border-radius: var(--form-input-block-border-radius); border-width: var(--form-input-block-border-width); background-color: var(--color-form-input-block-default-background); border-color: var(--color-form-input-block-defaul…`<br>+ 12 more rules           |
| `form-textarea`           | `& { --tw-shadow-color: var(--color-form-input-block-default-shadow); --tw-shadow: var(--tw-shadow-color); width: 100%; outline: none; border-style: solid; padding-inline: var(--form-input-block-padding-x); padding-block: var(--form-input-block-padding-y); border-radius: var(--form-input-block-border-radius); border-width: var(--form-input-block-border-width); background-color: var(--color-form-input-block-default-background); border-color: var(--color-form-input-block-defaul…`<br>+ 5 more rules            |
| `search-field-input`      | `& { --tw-shadow-color: var(--color-form-input-block-default-shadow); --tw-shadow: var(--tw-shadow-color); width: 100%; outline: none; border-style: solid; padding-inline: var(--form-input-block-padding-x); padding-block: var(--form-input-block-padding-y); border-radius: var(--form-input-block-border-radius); border-width: var(--form-input-block-border-width); background-color: var(--color-form-input-block-default-background); border-color: var(--color-form-input-block-defaul…`<br>+ 9 more rules            |
| `is-invalid`              | `:is(.form-select:user-invalid ~ .form-select-icon, .form-select.is-invalid ~ .form-select-icon) { color: var(--color-form-input-block-invalid-icon); background-color: var(--color-form-input-block-invalid-icon); }`<br>`:is(.form-group:has(:user-invalid) .form-invalid-message, .form-group:has(.is-invalid) .form-invalid-message) { display: block; }`                                                                                                                                                                   |
| `form-select-wrapper`     | `position: relative;`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `form-select-icon`        | `:is(.form-select ~ .form-select-icon) { pointer-events: none; position: absolute; top: 50%; transform: translateY(-50%); object-fit: contain; object-position: center; overflow: visible; line-height: 100%; right: var(--form-input-block-padding-x); width: var(--form-input-block-icon-size); height: var(--form-input-block-icon-size); font-size: var(--form-input-block-icon-size); color: var(--color-form-input-block-default-icon); }`<br>+ 3 more rules                                                              |
| `search-field`            | `position: relative;`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `search-field-icon`       | `:is(.search-field-input ~ .search-field-icon) { pointer-events: none; position: absolute; top: 50%; transform: translateY(-50%); object-fit: contain; object-position: center; overflow: visible; line-height: 100%; right: var(--form-input-block-padding-x); width: var(--form-input-block-icon-size); height: var(--form-input-block-icon-size); font-size: var(--form-input-block-icon-size); color: var(--color-form-input-block-default-icon); }`<br>+ 2 more rules                                                      |
| `form-inline-input-group` | `& > :not([hidden]) ~ :not([hidden]) { margin-top: var(--form-input-inline-space-y); }`                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `form-radio`              | `& { position: relative; user-select: none; }`<br>`& input[type="radio"], .form-radio  input[type="checkbox"], .form-radio:before { content: ""; position: absolute; left: 0; top: calc(var(--typography-form-inline-line-height) / 2); transform: translateY(-50%); width: var(--form-input-inline-input-size); height: var(--form-input-inline-input-size); }`<br>`& input[type="radio"], .form-radio  input[type="checkbox"] { opacity: 0; cursor: pointer; }`<br>`&:has(:checked):after { opacity: 1; }`<br>+ 13 more rules |
| `form-checkbox`           | `& { position: relative; user-select: none; }`<br>`& input[type="radio"], .form-checkbox  input[type="checkbox"], .form-checkbox:before { content: ""; position: absolute; left: 0; top: calc(var(--typography-form-inline-line-height) / 2); transform: translateY(-50%); width: var(--form-input-inline-input-size); height: var(--form-input-inline-input-size); }`<br>`& input[type="radio"], .form-checkbox  input[type="checkbox"] { opacity: 0; cursor: pointer; }`<br>+ 14 more rules                                   |
| `form-invalid-message`    | `& { display: none; color: var(--color-form-invalid-message); }`<br>`:is(.form-group:has(:user-invalid) .form-invalid-message, .form-group:has(.is-invalid) .form-invalid-message) { display: block; }`                                                                                                                                                                                                                                                                                                                         |
| `form-file-input`         | `& { cursor: pointer; padding: 0; height: calc(var(--form-input-block-padding-y) * 2 + var(--typography-form-block-line-height)); display: flex; align-items: center; }`<br>+ 2 more rules                                                                                                                                                                                                                                                                                                                                      |

<!-- /generated -->

## Next.js

`@systhemaui/next` re-exports the field components from `@systhemaui/react` unchanged. They render on the server; only a module that passes event handlers (like the `requiredMode="any"` example) needs `'use client'`.

## Accessibility

Every field wires its parts through `FormFieldShell`, the shared wrapper behind all eleven fields:

- The label is a `<label for>` pointing at the control's `id` (`id` defaults to `name`).
- The description gets the id `<id>-description` and the error `<id>-error`. Both ids are added to the control's `aria-describedby`, after any `aria-describedby` you pass yourself.
- When `invalidMessage` is set, the control gets `aria-invalid="true"`, and the message is a `role="alert"` so it is announced when it appears.
- `RadioField` renders its items in a `role="radiogroup"` and `CheckboxField` in a `role="group"`, both named by the group label through `aria-labelledby` (`<name>-label`). For these, `aria-describedby` and `aria-invalid` go on the group element.
- The required asterisk after the label is decorative CSS; assistive technology reads the `required` attribute.

See [Accessibility](https://docs.systhema.app/ar/concepts/accessibility.md#forms) for what the components guarantee across the system.

## Related

- [Form](https://docs.systhema.app/ar/components/form.md)
- [Form controls](https://docs.systhema.app/ar/components/form-controls.md)
- [Select](https://docs.systhema.app/ar/components/form-select.md) and [Radios and checkboxes](https://docs.systhema.app/ar/components/form-choice.md)
- [Form block](https://docs.systhema.app/ar/payload/blocks/form.md) and the [Forms module](https://docs.systhema.app/ar/payload/forms.md)
