Building with oks-ui
Compose a form
Form collects values and fires onSubmit. FormFieldSet is one component that renders every field type. That's the whole model.
The shape
import { Form, FormFieldSet } from "oks-ui/form-field-set";
import { Button } from "oks-ui/button";
function ProfileForm() {
return (
<Form onSubmit={(data) => saveProfile(data)}>
<FormFieldSet type="text" name="name" label="Name" />
<FormFieldSet
type="email"
name="email"
label="Email"
validation={{ rules: { required: true, email: true } }}
/>
<FormFieldSet
type="select"
name="role"
label="Role"
options={[
{ label: "Admin", value: "admin" },
{ label: "Editor", value: "editor" },
]}
/>
<FormFieldSet type="switch" name="notify" label="Email me updates" defaultChecked />
<Button type="submit" color="primary">Save</Button>
</Form>
);
}Every field type is the same call
Change type and the right control renders — with the right label placement, the right keyboard behaviour, and the right ARIA. The types: text, textarea, select, password, otp, phone, range, file, switch, radio, checkbox, datepicker (and text also covers number / url / search / color).
Validation
Pass validation.rules. Errors render inline and set aria-invalid; the message is wired to the input with aria-describedby. Nothing else to do.
<FormFieldSet
type="password"
name="password"
label="Password"
validation={{
rules: { required: true, minLength: 8 },
messages: { minLength: "Use at least 8 characters." },
}}
/>Multi-step flows
For a wizard, reach for SteppedForm — it's built on FormFieldSet, tracks progress, and validates per step.
Controlled when you need it
Every FormFieldSet accepts
value + onChange for controlled use — useful when one field's value drives another field's options, or when you filter a table as the user types.