Field
Introduction
A foundational component for form fields, managing labels, helper text, error messages, and validation state. It follows the "Smart Switcher" pattern, automatically hydrating as an interactive island when needed.
Usage
Smart Auto-Hydration
The Field component automatically determines if it should be interactive. It becomes an island if any of the following props are provided: interactive, onValueChange, value, defaultValue, validator, or minLength.
Validation
Field supports built-in and custom validation.
Minimum Length
Using minLength will automatically show an error message if the input is too short.
<Field
label="Username"
minLength={5}
placeholder="Enter at least 5 characters"
/>
Custom Validator
The validator prop accepts a function that returns true for valid, false for invalid (using default error message), or a string as a custom error message.
<Field
label="Email"
validator={(value) => {
if (!value.includes("@")) return "Must be a valid email";
return true;
}}
/>
The simplest way to use Field is with the flattened props. It will automatically render an input.
import { Field } from "../components/ui";
export default function MyPage() {
return (
<Field
label="Username"
helperText="Choose a unique username."
placeholder="Type here..."
minLength={3}
/>
);
}
Fieldset inheritance
A Field nested inside a Fieldset inherits the group's disabled/required
state, and its invalid state too — but only when the Field has no
validation of its own (no validator/minLength and no explicit invalid
prop). An explicit prop on Field always wins over the inherited value.
<Fieldset legend="Shipping address" disabled>
{/* inherits disabled — no need to repeat it */}
<Field label="Street" />
</Fieldset>
Note that native <fieldset disabled> also disables every descendant form
control at the browser level regardless of this inheritance — the inherited
disabled prop only keeps the Field's own styling/ARIA in sync with that,
it can't be overridden back to enabled from inside a disabled fieldset.
Composition
For more control, you can wrap other components like Textarea or custom inputs. The Field provides context to its children.
import { Field, Textarea } from "../components/ui";
export default function MyPage() {
return (
<Field
label="Bio"
helperText="Tell us about yourself."
minLength={10}
>
<Textarea placeholder="A short bio" />
</Field>
);
}
CMS Page Builder
This component is available as a field block in the Page Builder (content/pages/*.json):
{
"type": "field",
"label": "Username",
"helperText": "Choose a unique username.",
"required": true
}
Props
| Prop | Type | Description |
|---|---|---|
children | any | Content to be rendered inside the component. If provided, the internal input is not rendered. |
class | string | Custom CSS classes. |
id | string | Unique identifier. If not provided, one is generated. |
label | Child | The label for the field. |
helperText | Child | The helper text for the field. |
errorText | Child | The error text for the field. If provided as a string, it will be used as the message. |
disabled | boolean | Whether the field is disabled. |
invalid | boolean | Whether the field is in an invalid state. |
required | boolean | Whether the field is required. |
readOnly | boolean | Whether the field is read-only. |
value | string | The current value (forces interactive mode). |
defaultValue | string | The initial value (forces interactive mode). |
onValueChange | (val: string) => void | Callback triggered when value changes (forces interactive mode). |
minLength | number | Minimum length validation (forces interactive mode). |
| validator | `(val: string) => boolean \ | string` | Custom validation function (forces interactive mode). |
| interactive | boolean | Forces hydration as an island. |