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.
A label, a control and a description. Clicking the label focuses the input, and screen readers read the description after the label.
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.
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”.
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.
<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.
<FieldCounter /> finds the text control in its field and counts against its maxLength. It only listens, so typing is never slowed or changed.
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.
Use orientation="horizontal" to put the checkbox beside its label. Inside a <FieldSet />, the legend names the whole group.
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 /> groups related fields under a <FieldLegend />, which becomes the group’s accessible name. Lay fields out side by side with a plain grid.
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.
Disabling a <FieldSet /> disables every field and control inside it. Pass disabled to a single <Field /> to disable just that one.
Labels, descriptions and errors wrap inside narrow forms, including unbroken strings, and never push the layout wider.
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 | – |
- InputA text input with three sizes, invalid and read-only states, native validation styling and a 16px touch font so phones never zoom in.
- MotionThe easing curves, durations and reduced-motion check every component animates with, plus hooks for size morphs and sliding highlights.
- SeparatorA hairline that divides content horizontally or vertically, with an optional label and a decorative mode for purely visual lines.
- useComposedRefKeeps a ref to your own element while still forwarding it to whatever ref the parent passed in.
- useMergedRefCombines any number of callback and object refs into one, with React 19 ref cleanup for each of them.
- CalendarA date grid for single, range and multiple selection, with sliding months, range previews and touch-sized days.
Used in Pro blocks
Blocks that build on Field.
- API keysThe API keys page of an AI product. Handles a secret shown only once, revoke with undo and rotation with a grace period.
- BillingThe plan and usage page of an AI product. Handles credits running out mid-cycle, plan changes with proration and card errors.
- ModelsThe Models page of an AI product’s settings. Handles the default model and effort, providers and custom servers with a connection test.
- NotificationsThe Notifications section of an AI product’s settings. Handles a channel by event grid, quiet hours and browser permission for push.
- ProfileThe Profile section of an AI product’s settings. Handles photo cropping, a username checked as you type and email changes confirmed by code.
- SecurityThe Security section of an AI product’s settings. Handles sessions, two-factor with recovery codes and deletion behind a typed confirmation.
Every block comes with HextaUI Pro, and Prompt Input is free.