Forms
A guide to building forms with Cubby UI components
Overview
Cubby UI provides three components for building accessible forms: Form, Field, and Fieldset. These wrap Base UI's form primitives, providing native constraint validation, custom validation, accessible labeling, error display, and integration with third-party libraries.
Quick start
Labeling form controls
Every form control needs an accessible label. The correct label component depends on the control type.
Input-type controls
Use FieldLabel to label these controls:
For Checkbox, Radio, and Switch, wrap the control inside FieldLabel for implicit labeling:
Trigger-based controls
These controls have their own label component. Do not use FieldLabel for them:
Descriptions
FieldDescription adds accessible helper text to any field, regardless of control type:
Grouping controls with Fieldset
Use Fieldset with FieldsetLegend when a single label applies to multiple controls, such as radio groups, checkbox groups, or multi-thumb sliders. Compose with the render prop to merge the fieldset element with a group component:
Use FieldItem to wrap each individual option so it gets its own label and description.
Validation
Constraint validation
Use native HTML attributes on FieldControl (required, pattern, minLength, type, min, max, etc.) and FieldError to display the browser's native error message:
Per-state custom messages
To override native messages (for i18n or branding), use multiple FieldError components with the match prop — each one renders only for the specified validity state:
Available match values: valueMissing, typeMismatch, patternMismatch, tooShort, tooLong, rangeUnderflow, rangeOverflow, stepMismatch, badInput, customError.
Custom validation
Use the validate prop on Field for custom logic. Supports async functions:
Server-side validation
Pass errors from your server to Form's errors prop. Keys match field name attributes:
Submitting data
Native onSubmit
Use onSubmit with FormData for standard form submission:
JavaScript object with onFormSubmit
Use onFormSubmit to receive values as a plain object. preventDefault is called automatically:
Third-party library integration
React Hook Form
Use Controller to bridge RHF state with Field's invalid, dirty, and touched props. Use onValueChange on FieldControl (not onChange) and match={!!error} on FieldError:
For RHF to focus invalid fields, forward ref to the underlying control - typically via inputRef on wrapper components (Select, Switch, Checkbox, RadioGroup) or directly as ref on input-like components.
TanStack Form
Use form.Field (TanStack's field) to manage state, and Field (Cubby UI) for the UI. Note: the Base UI Form component isn't needed with TanStack Form - use a native <form> element instead.