How to Use
FormFieldSet
FormFieldSet is the single component for rendering any form field — it supports 17 field types via the type prop: text, number, url, search, color, password, email, textarea, select, radio, checkbox, switch, datepicker, phone, range, otp, and file. Each field type renders the appropriate HTML input with consistent label placement (top or floating), validation, error states, and styling. Always pair FormFieldSet with the <Form> component, which provides context-driven validation — it tracks all field values, validates against rules, and collects submitted data into a single object. The useFormContext hook gives child components access to the form state for advanced patterns like conditional fields. FormFieldSet supports label/description text, start/end content, prefix/suffix text, radius control, and size variants. This component eliminates the need for separate TextField, SelectField, SwitchField, etc. components — one component handles all field types with a consistent API.
Complete Signup Form
The fundamental pattern is wrapping FormFieldSet components inside <Form>. The Form component's onSubmit callback receives all field values as a single object keyed by each field's name prop. This example shows a full signup form with text, email, select, password, and switch fields — each with validation rules. The Button with type="submit" triggers form submission. Validation rules run on submit and on blur, showing error messages below each field.
function SignupForm() {
return (
<Form onSubmit={(data) => alert(JSON.stringify(data, null, 2))}>
<div className="flex flex-col gap-4">
<FormFieldSet
type="text"
name="name"
label="Full Name"
validation={{ rules: { required: true } }}
/>
<FormFieldSet
type="email"
name="email"
label="Email Address"
validation={{ rules: { required: true, email: true } }}
/>
<FormFieldSet
type="select"
name="role"
label="Role"
options={[
{ label: "Developer", value: "dev" },
{ label: "Designer", value: "design" },
]}
/>
<FormFieldSet
type="password"
name="password"
label="Password"
validation={{ rules: { required: true } }}
/>
<FormFieldSet
type="switch"
name="terms"
label="Accept Terms"
/>
<Button type="submit" color="primary">
Create Account
</Button>
</div>
</Form>
);
}Validation Rules and Error Messages
The validation prop accepts a rules object and an optional message object. Built-in rule types include: required (value must be truthy), email (must match email pattern), minLength/maxLength (string length), min/max (numeric range), pattern (custom regex). When a rule fails, the field shows the default error message — or your custom message from the message object. The error prop lets you manually set an error message from external validation (e.g., server-side errors). The touched prop tracks whether the user has interacted with the field — errors only appear when touched is true AND error is set, preventing premature error display.
function LoginForm() {
const [serverError, setServerError] = useState("");
async function handleLogin(data: any) {
const res = await fetch("/api/login", {
method: "POST",
body: JSON.stringify(data),
});
if (!res.ok) {
const err = await res.json();
setServerError(err.message);
}
}
return (
<Form onSubmit={handleLogin}>
<div className="flex flex-col gap-4">
<FormFieldSet
type="email"
name="email"
label="Email"
placeholder="you@example.com"
validation={{
rules: { required: true, email: true },
message: {
required: "Email is required",
email: "Please enter a valid email address",
},
}}
/>
<FormFieldSet
type="password"
name="password"
label="Password"
validation={{ rules: { required: true } }}
error={serverError}
/>
<Button type="submit" color="primary">
Sign In
</Button>
</div>
</Form>
);
}All 17 Field Types
FormFieldSet renders any of 17 types via the type prop. Each type renders the appropriate HTML input element with consistent label, description, error, and styling behavior. Text-like types (text, email, number, password, url, search, color, textarea) accept standard input props like placeholder, min, max, and step. Selection types (select, radio, checkbox, switch) require an options array of { label, value } objects. Specialized types (datepicker, phone, range, otp, file) accept type-specific props — see the Props Reference below for details.
// Text-like inputs
<FormFieldSet type="text" name="text" label="Text" />
<FormFieldSet type="number" name="num" label="Number" min={0} max={100} />
<FormFieldSet type="email" name="email" label="Email" />
<FormFieldSet type="password" name="pw" label="Password" />
<FormFieldSet type="url" name="url" label="URL" />
<FormFieldSet type="search" name="q" label="Search" />
<FormFieldSet type="color" name="c" label="Color" />
<FormFieldSet type="textarea" name="bio" label="Bio" rows={3} />
// Selection types
<FormFieldSet type="select" name="role" label="Role"
options={[{ label: "A", value: "a" }]} />
<FormFieldSet type="radio" name="gender" label="Gender"
options={[{ label: "Male", value: "m" }]} />
<FormFieldSet type="checkbox" name="hobbies" label="Hobbies"
options={[{ label: "Code", value: "code" }]} />
<FormFieldSet type="switch" name="on" label="Enable" />
// Specialized types
<FormFieldSet type="datepicker" name="dob" label="Date" />
<FormFieldSet type="phone" name="tel" label="Phone" defaultCountryCode="us" />
<FormFieldSet type="range" name="vol" label="Volume" min={0} max={100} />
<FormFieldSet type="otp" name="code" label="OTP" length={4} />
<FormFieldSet type="file" name="doc" label="Upload" accept="image/*" />Conditional Fields with useFormContext
The useFormContext hook gives nested components access to the form's current values, validation state, and methods (getValues, setValue, setError, etc.). This enables conditional fields — showing or hiding fields based on the value of another field. In this example, the contact method select controls whether an email field or phone field appears below. The conditional component reads the current value of contactMethod and renders the appropriate field. useFormContext only works inside a <Form> wrapper.
import { Form, FormFieldSet, Button, useFormContext } from 'oks-ui';
function DynamicForm() {
return (
<Form onSubmit={(data) => console.log(data)}>
<div className="flex flex-col gap-4">
<FormFieldSet
type="select"
name="contactMethod"
label="Contact Method"
options={[
{ label: "Email", value: "email" },
{ label: "Phone", value: "phone" },
]}
/>
<ConditionalFields />
<Button type="submit">Submit</Button>
</div>
</Form>
);
}
function ConditionalFields() {
const { getValues } = useFormContext();
const method = getValues().contactMethod;
if (method === "email") {
return <FormFieldSet type="email" name="email" label="Email Address" />;
}
if (method === "phone") {
return <FormFieldSet type="phone" name="phone" label="Phone Number" />;
}
return null;
}Label Placement and Styling
The labelPlacement prop controls where the label renders relative to the input. "top" (default) places the label above the input — standard for most forms. "floating" places the label inside the input border that animates up when the field is focused or has a value — a modern, space-saving pattern. The variant prop changes the input's visual style: "default" has a standard border, "soft" has a filled light background, and "underline" draws only a bottom border. The size prop (xs through xl) controls the input dimensions consistently across all field types. The radius prop overrides the border radius of the input container.
<FormFieldSet type="text" name="top" label="Top Label" labelPlacement="top" />
<FormFieldSet type="text" name="float" label="Floating Label" labelPlacement="floating" />
<FormFieldSet type="text" name="v1" label="Default" variant="default" />
<FormFieldSet type="text" name="v2" label="Soft" variant="soft" />
<FormFieldSet type="text" name="v3" label="Underline" variant="underline" />Accessibility
Each FormFieldSet renders with a proper <label> element connected to the input via htmlFor/id. Error messages are linked to the input via aria-describedby. Required fields set aria-required="true". The validation state sets aria-invalid="true" when there is an error. The description prop renders helper text below the field, linked via aria-describedby. When used inside a Form, the form's submit handler follows standard HTML form submission behavior with validation. The phone type uses an international phone input with country code selection that is keyboard accessible. The datepicker supports keyboard navigation (arrows, Enter, Escape). The OTP type automatically moves focus to the next digit input after entry.
Props Reference
All available props for FormFieldSet. See the full API reference page for interactive playground examples.
| Prop | Type | Default | Description |
|---|---|---|---|
| type | FormFieldType | "text" | The field type to render. One of: text, number, url, search, color, password, email, textarea, select, radio, checkbox, switch, datepicker, phone, range, otp, file. |
| name | string | — | Field name — used as the key in form data and for validation context. |
| label | ReactNode | — | Label text or element displayed above or floating over the input. |
| description | ReactNode | — | Helper text displayed below the field. |
| size | "xs" | "xs-sm" | "sm" | "md" | "lg" | "xl" | "md" | Control size. |
| variant | "default" | "soft" | "underline" | "default" | Visual style variant. |
| color | "default" | "primary" | "info" | "success" | "warning" | "danger" | "secondary" | "default" | Color accent for the field. |
| colorDepth | number | 500 | Color intensity (50–950). |
| labelPlacement | "top" | "floating" | "top" | Where the label renders. Switch also supports 'left' and 'right'. |
| validation | FieldValidation | — | Validation rules and error messages. e.g. { rules: { required: true, email: true }, message: { email: 'Invalid email' } }. |
| error | ReactNode | — | Error message to display. When set, field is visually errored. |
| touched | boolean | false | Whether the field has been interacted with. Shows error only when touched + error is set. |
| disabled | boolean | false | Disable the input element. |
| id | string | — | HTML id attribute. Auto-generated if omitted. |
| placeholder | string | — | Placeholder text (text-like types only). |
| required | boolean | false | HTML required attribute (use validation.rules.required for validation). |
| options | SelectOption[] | — | Array of { label, value } options for select, radio, and checkbox types. |
| native | boolean | false | Use native <select> instead of custom dropdown (select only). |
| strongPassword | object | — | Strong password config: { minLength, minUpper, minLower, minNumber, minSpecial }. |
| revealToggle | boolean | true | Show/hide password visibility toggle button. |
| rows | number | 3 | Number of visible text rows (textarea only). |
| showLengthCounter | boolean | false | Show character count below the textarea. |
| range | boolean | false | Enable range selection (start/end dates). |
| withTime | boolean | false | Include time picker in the date selection. |
| showPresets | boolean | true | Show preset date ranges (Today, This Week, etc.). |
| displayFormat | "pretty" | "iso" | "pretty" | Date display format. |
| clearable | boolean | true | Show a clear button to reset the date. |
| minDate | Date | string | — | Minimum selectable date. |
| maxDate | Date | string | — | Maximum selectable date. |
| blockedDates | Array<Date | string> | — | Dates that cannot be selected. |
| monthsToShow | 1 | 2 | 1 | Number of calendar months to display. |
| length | number | 4 | Number of OTP digit inputs. |
| format | "digits" | "alphanumeric" | "digits" | Allowed character format. |
| ui | "segmented" | "single" | "segmented" | OTP UI style — separate boxes or single input. |
| accept | string | — | Accepted file MIME types, e.g. 'image/*'. |
| multiple | boolean | false | Allow multiple file selection. |
| maxFiles | number | — | Maximum number of files. |
| maxFileSize | number | — | Maximum file size in bytes. |
| ui | "inline" | "dropzone" | "inline" | File field UI variant. |
| preview | "none" | "thumbnails" | "thumbnails" | File preview mode. |
| dropLabel | string | "Drag & drop files here" | Text shown in dropzone area. |
| browseLabel | string | "Browse files" | Button label for file browser. |
| defaultCountryCode | string | "us" | Default country code (ISO 3166-1 alpha-2). |
| countryCodeMode | "select" | "hidden" | "select" | Show country code selector or auto-detect. |
| numbersOnly | boolean | false | Only allow numeric characters. |
| phonePlaceholder | string | — | Placeholder for the phone number input. |
| min | number | 0 | Minimum value. |
| max | number | 100 | Maximum value. |
| step | number | 1 | Step increment. |
| showValue | boolean | true | Display the current value as a label. |
| marks | number[] | — | Array of tick mark values along the slider. |
| selection | "single" | "range" | "single" | Single value or range selection. |
| checked | boolean | — | Controlled checked state. |
| defaultChecked | boolean | false | Default checked state (uncontrolled). |
| showStateText | boolean | false | Show on/off text labels next to the switch. |
| checkedText | ReactNode | "On" | Text shown when switch is on. |
| uncheckedText | ReactNode | "Off" | Text shown when switch is off. |
| stateTextPlacement | "before" | "after" | "both" | "after" | Where state labels render relative to the switch. |
| labelPlacement | "top" | "left" | "right" | "floating" | "top" | Label position (switch supports left/right in addition to top/floating). |
| radius | "none" | "xs" | "xs-sm" | "sm" | "md" | "lg" | "full" | "md" | Border radius of the input. |
| rounded | "none" | "xs" | "xs-sm" | "sm" | "md" | "lg" | "full" | — | Alternative radius alias. |
| startIcon | ReactNode | — | Icon/element at the start of the input. |
| endIcon | ReactNode | — | Icon/element at the end of the input. |
| prefix | ReactNode | — | Text prefix inside the input before the value. |
| suffix | ReactNode | — | Text suffix inside the input after the value. |
| min | number | — | Minimum allowed value. |
| max | number | — | Maximum allowed value. |
| step | number | 1 | Step increment for the control. |
Best Practices
- ◆Always wrap FormFieldSet in a <Form> component to get context-driven validation and data collection — FormFieldSet alone does not validate without Form context
- ◆Each FormFieldSet must have a unique name prop — this is the key in the data object received by Form's onSubmit callback
- ◆Use validation.rules for declarative validation (required, email, minLength, maxLength, min, max, pattern); use the error prop for server-side or programmatic errors
- ◆For custom error messages, pass a message object where keys match rule names: message: { required: 'Required', email: 'Invalid email' }
- ◆Set type="submit" on the Button inside a Form to trigger submission; set type="button" on cancel/dismiss buttons to prevent form submission
- ◆The options prop is required for select, radio, and checkbox types — it accepts { label: string, value: string }[]
- ◆Use labelPlacement="floating" for compact forms like login screens or settings panels where vertical space is limited
- ◆useFormContext() from 'oks-ui' provides getValues, setValue, setError, reset, and form state — use it in child components for conditional fields and cross-field validation
- ◆The file type supports accept, multiple, maxFiles, maxFileSize, preview, and dropLabel props — configure these to control file upload behavior
- ◆The datepicker supports range selection, time picker, preset ranges, min/max dates, and blocked dates — see the props reference for full configuration