Field
Labels, descriptions and errors wired to their control, with validation states and layouts for forms.
pnpm dlx shadcn@latest add https://hextaui.com/r/field.jsonAdds the component, the HextaUI theme tokens and any HextaUI components it depends on.
Add the theme tokens to your global CSS, if you haven’t yet.
Install the dependencies.
pnpm add @base-ui/react @tabler/icons-react class-variance-authority cnCopy and paste the following code into your project.
components/ui/field.tsx components/ui/input.tsx components/ui/number-flow.tsx components/ui/separator.tsx lib/motion.ts Update the import paths to match your project setup.
Put any HextaUI control inside a <Field /> and it’s labelled, described and validated automatically. There’s no need to wire up id, htmlFor or aria-describedby by hand.
One control with its label, help text and validation.
A switch or checkbox with its text beside it.
A label that wraps a whole field, so the card is the click target.
Related fields, spaced evenly.
A titled group of fields, or a radio or checkbox group with an item per option.
Input
A label, a control and a description. Clicking the label focuses the input, and screen readers read the description after the label.
Shown on your profile. You can change it once a month.
Validation
Native constraints like required and minLength are checked on blur. Give each <FieldError /> a match to word the message per problem. An empty required field is only flagged after it has been edited, so tabbing past it doesn’t shout.
At least 8 characters.
Custom validation
Pass validate to check anything, including async lookups. Return a message to fail or nothing to pass. With validationMode="onChange" and validationDebounceTime, it runs while typing without firing on every key. Try “ada”.
Checked as you type.
Required and optional
Set indicator on a FieldGroup, FieldSet or Field and every label inside marks itself from its control's required attribute. "optional" tags the fields people can skip, which reads calmer when most fields are required. "required" adds an asterisk. The mark is hidden from screen readers because the control already announces it.
Status
<FieldStatus /> draws a check once an edited field passes validation, and shows an alert icon while it fails. It follows the field's validationMode, so it never judges a field before validation has run.
Try HX-2026.
Character count
<FieldCounter /> finds the text control in its field and counts against its maxLength. It only listens, so typing is never slowed or changed.
Clears after 24 hours.
Errors from a form library or server
Pass invalid to the field and an errors array to <FieldError />. It takes the { message } shape React Hook Form and most schema libraries return. Duplicates are dropped, and several messages become a list. When the messages change, the new ones fade in and the height eases to fit, so nothing below jumps. Submit empty, then fix one rule at a time.
Checkboxes
Use orientation="horizontal" to put the checkbox beside its label. Inside a <FieldSet />, the legend names the whole group.
Choice cards
Wrap a whole field in <FieldLabel /> to make the card the click target. Use <FieldTitle /> inside, since labels can’t nest. The card tints when checked and shows the focus ring when its checkbox is focused.
Fieldset
<FieldSet /> groups related fields under a <FieldLegend />, which becomes the group’s accessible name. Lay fields out side by side with a plain grid.
Responsive
orientation="responsive" stacks the label and control in narrow spaces and puts them side by side once the surrounding <FieldGroup /> is wide enough. It responds to the group’s width, not the window’s.
Shown next to your messages.
Linked from your profile.
Disabled
Disabling a <FieldSet /> disables every field and control inside it. Pass disabled to a single <Field /> to disable just that one.
Long content
Labels, descriptions and errors wrap inside narrow forms, including unbroken strings, and never push the layout wider.
averyveryverylongunbrokendescriptionthatmustwrapinsteadofoverflowing
Right to left
Text, checkbox placement and error lists follow the reading direction.
سنرسل رابط التأكيد إلى هذا العنوان.
- The label, description and visible errors are linked to the control for you, so screen readers announce all three when it’s focused.
- Invalid controls get
aria-invalid, which also draws their error ring. - Errors are not live regions. They’re read when the control is focused, so validating on change doesn’t interrupt typing. On submit, move focus to the first invalid field.
- Errors grow and fade in place instead of pushing content down. With reduced motion on, they appear without animating.
Built on the Base UI field and fieldset. Every part accepts the props of the element or primitive it renders.
| Prop | Type | Default |
|---|---|---|
orientation | "vertical" | "horizontal" | "responsive" | "vertical" |
indicatorMark the label from the control's required attribute. Inherited from FieldGroup or FieldSet. | "required" | "optional" | null | – |
nameIdentifies the field when the form is submitted. | string | – |
validateReturn one or more messages to fail, or nothing to pass. Async is supported. | (value, formValues) => string | string[] | null | Promise<…> | – |
validationMode | "onSubmit" | "onBlur" | "onChange" | "onSubmit" |
validationDebounceTimeMilliseconds to wait between onChange validations. | number | 0 |
invalidSet it from a form library or server response. | boolean | – |
disabled | boolean | false |
dirty | boolean | – |
touched | boolean | – |
actionsRefValidate the field imperatively. | RefObject<{ validate: () => void }> | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="field" | Target fields in CSS. |
data-orientation | The current orientation. |
data-disabled | Present when the field is disabled. |
data-valid | Present when the field is valid. |
data-invalid | Present when the field is invalid. |
data-dirty | Present once the value has changed from its initial value. |
data-touched | Present once the control has been focused and left. |
data-filled | Present when the control has a value. |
data-focused | Present while the control has focus. |
| Prop | Type | Default |
|---|---|---|
nativeLabelSet false when render swaps the label for a non-label element. | boolean | true |
optionalTextText shown with indicator="optional". | ReactNode | "Optional" |
render | ReactElement | (props, state) => ReactElement | <label> |
| Attribute | Description |
|---|---|
data-slot="field-label" | Target labels in CSS. Outside a field it renders a plain label, which is how choice cards work. |
data-disabled | Present when the field is disabled. |
data-valid | Present when the field is valid. |
data-invalid | Present when the field is invalid. |
data-dirty | Present once the value has changed from its initial value. |
data-touched | Present once the control has been focused and left. |
data-filled | Present when the control has a value. |
data-focused | Present while the control has focus. |
An icon that reflects the field's validity. It's decorative, since the error message carries the meaning.
| Attribute | Description |
|---|---|
data-slot="field-status" | Target the status icon in CSS. |
| Prop | Type | Default |
|---|---|---|
threshold | number | 10% of maxLength, at most 20 |
announcementMessage for screen readers when the count crosses the threshold or hits the limit. | (remaining: number) => string | – |
| Attribute | Description |
|---|---|
data-slot="field-counter" | Target the counter in CSS. |
data-state="near" | "limit" | Present within the threshold, and at the limit. |
| Prop | Type | Default |
|---|---|---|
render | ReactElement | (props, state) => ReactElement | <p> |
| Attribute | Description |
|---|---|
data-slot="field-description" | Target descriptions in CSS. |
data-disabled | Present when the field is disabled. |
data-valid | Present when the field is valid. |
data-invalid | Present when the field is invalid. |
data-dirty | Present once the value has changed from its initial value. |
data-touched | Present once the control has been focused and left. |
data-filled | Present when the control has a value. |
data-focused | Present while the control has focus. |
| Prop | Type | Default |
|---|---|---|
matchShow only for this validity problem. true always shows it. | boolean | "valueMissing" | "typeMismatch" | "tooShort" | "tooLong" | "patternMismatch" | "rangeOverflow" | "rangeUnderflow" | "stepMismatch" | "badInput" | "customError" | "valid" | – |
errorsErrors from a form library or server. Shown when the list has a message. | Array<{ message?: string } | undefined> | – |
childrenDefaults to the validation message. | ReactNode | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="field-error" | Target errors in CSS. |
data-starting-style | Present while the error grows in. |
data-ending-style | Present while the error collapses. |
data-disabled | Present when the field is disabled. |
data-valid | Present when the field is valid. |
data-invalid | Present when the field is invalid. |
data-dirty | Present once the value has changed from its initial value. |
data-touched | Present once the control has been focused and left. |
data-filled | Present when the control has a value. |
data-focused | Present while the control has focus. |
Stacks a label, description and error beside a control in a horizontal field.
| Prop | Type | Default |
|---|---|---|
render | ReactElement | (props, state) => ReactElement | <div> |
A label-styled title for content inside a <FieldLabel />, such as choice cards.
| Prop | Type | Default |
|---|---|---|
render | ReactElement | (props, state) => ReactElement | <div> |
Spaces fields apart and is the container that responsive fields measure.
| Prop | Type | Default |
|---|---|---|
indicatorApplies to every field inside. | "required" | "optional" | null | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| Prop | Type | Default |
|---|---|---|
indicatorApplies to every field inside. | "required" | "optional" | null | – |
disabledDisables every field inside. | boolean | false |
render | ReactElement | (props, state) => ReactElement | <fieldset> |
| Attribute | Description |
|---|---|
data-slot="field-set" | Target fieldsets in CSS. |
data-disabled | Present when the fieldset is disabled. |
| Prop | Type | Default |
|---|---|---|
variantlabel matches the size of a field label. | "legend" | "label" | "legend" |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="field-legend" | Target legends in CSS. |
data-variant | The current variant. |
A <Separator /> spaced for forms, and accepts all of its props.
| Prop | Type | Default |
|---|---|---|
childrenOptional text shown in the middle of the line. | ReactNode | – |
alignWhere the text sits along the line. | "start" | "center" | "end" | "center" |
decorativeHide a plain line from screen readers when it is only visual. | boolean | false |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="field-separator" | Target field separators in CSS. |
data-content | Present when the separator has text. |
data-slot="separator-label" | The element that wraps the text. |
Wraps one checkbox or radio and its label inside a group, so each item can be disabled on its own.
| Prop | Type | Default |
|---|---|---|
disabled | boolean | false |
render | ReactElement | (props, state) => ReactElement | <div> |
Renders anything from the field’s validity state, for example a strength meter or a character counter.
| Prop | Type | Default |
|---|---|---|
children | (state: { validity, errors, error, value }) => ReactNode | – |