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.

import {FormFieldSet} from "oks-ui"

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.

PropTypeDefaultDescription
typeFormFieldType"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.
namestringField name — used as the key in form data and for validation context.
labelReactNodeLabel text or element displayed above or floating over the input.
descriptionReactNodeHelper 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.
colorDepthnumber500Color intensity (50–950).
labelPlacement"top" | "floating""top"Where the label renders. Switch also supports 'left' and 'right'.
validationFieldValidationValidation rules and error messages. e.g. { rules: { required: true, email: true }, message: { email: 'Invalid email' } }.
errorReactNodeError message to display. When set, field is visually errored.
touchedbooleanfalseWhether the field has been interacted with. Shows error only when touched + error is set.
disabledbooleanfalseDisable the input element.
idstringHTML id attribute. Auto-generated if omitted.
placeholderstringPlaceholder text (text-like types only).
requiredbooleanfalseHTML required attribute (use validation.rules.required for validation).
optionsSelectOption[]Array of { label, value } options for select, radio, and checkbox types.
nativebooleanfalseUse native <select> instead of custom dropdown (select only).
strongPasswordobjectStrong password config: { minLength, minUpper, minLower, minNumber, minSpecial }.
revealTogglebooleantrueShow/hide password visibility toggle button.
rowsnumber3Number of visible text rows (textarea only).
showLengthCounterbooleanfalseShow character count below the textarea.
rangebooleanfalseEnable range selection (start/end dates).
withTimebooleanfalseInclude time picker in the date selection.
showPresetsbooleantrueShow preset date ranges (Today, This Week, etc.).
displayFormat"pretty" | "iso""pretty"Date display format.
clearablebooleantrueShow a clear button to reset the date.
minDateDate | stringMinimum selectable date.
maxDateDate | stringMaximum selectable date.
blockedDatesArray<Date | string>Dates that cannot be selected.
monthsToShow1 | 21Number of calendar months to display.
lengthnumber4Number of OTP digit inputs.
format"digits" | "alphanumeric""digits"Allowed character format.
ui"segmented" | "single""segmented"OTP UI style — separate boxes or single input.
acceptstringAccepted file MIME types, e.g. 'image/*'.
multiplebooleanfalseAllow multiple file selection.
maxFilesnumberMaximum number of files.
maxFileSizenumberMaximum file size in bytes.
ui"inline" | "dropzone""inline"File field UI variant.
preview"none" | "thumbnails""thumbnails"File preview mode.
dropLabelstring"Drag & drop files here"Text shown in dropzone area.
browseLabelstring"Browse files"Button label for file browser.
defaultCountryCodestring"us"Default country code (ISO 3166-1 alpha-2).
countryCodeMode"select" | "hidden""select"Show country code selector or auto-detect.
numbersOnlybooleanfalseOnly allow numeric characters.
phonePlaceholderstringPlaceholder for the phone number input.
minnumber0Minimum value.
maxnumber100Maximum value.
stepnumber1Step increment.
showValuebooleantrueDisplay the current value as a label.
marksnumber[]Array of tick mark values along the slider.
selection"single" | "range""single"Single value or range selection.
checkedbooleanControlled checked state.
defaultCheckedbooleanfalseDefault checked state (uncontrolled).
showStateTextbooleanfalseShow on/off text labels next to the switch.
checkedTextReactNode"On"Text shown when switch is on.
uncheckedTextReactNode"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.
startIconReactNodeIcon/element at the start of the input.
endIconReactNodeIcon/element at the end of the input.
prefixReactNodeText prefix inside the input before the value.
suffixReactNodeText suffix inside the input after the value.
minnumberMinimum allowed value.
maxnumberMaximum allowed value.
stepnumber1Step 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