Skip to content
Primitives

Form

Accessible form scaffolding for react-hook-form — binds the okibi field primitives to a schema, wires `aria-invalid`/`aria-describedby`, and surfaces validation as a mono system line.

● LIVE

Your operator handle on the relay.

npx shadcn@latest add https://okibi.cndr.dev/r/form.json

Installation

Install the theme first, then add the component:

npx shadcn@latest add https://okibi.cndr.dev/r/form.json

Install the required dependencies:

npm install @radix-ui/react-slot react-hook-form

Add the okibi building blocks it composes: label.

Copy the source from the Code tab above into components/ui/form.tsx. It uses the cn helper and the okibi theme tokens — install the theme first if you haven't.

Usage

import { useForm } from "react-hook-form"

import { Input } from "@/components/ui/input"
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form"

const form = useForm({ defaultValues: { callsign: "" } })

<Form {...form}>
  <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
    <FormField
      control={form.control}
      name="callsign"
      rules={{ required: "Callsign is required." }}
      render={({ field }) => (
        <FormItem>
          <FormLabel>Callsign</FormLabel>
          <FormControl>
            <Input placeholder="VANTA-7" {...field} />
          </FormControl>
          <FormDescription>Your operator handle on the relay.</FormDescription>
          <FormMessage />
        </FormItem>
      )}
    />
  </form>
</Form>

Anatomy

<Form {...form}>
  <form onSubmit={form.handleSubmit(onSubmit)}>
    <FormField
      control={form.control}
      name="callsign"
      render={({ field }) => (
        <FormItem>
          <FormLabel>Callsign</FormLabel>
          <FormControl>
            <Input {...field} />
          </FormControl>
          <FormDescription>Your operator handle.</FormDescription>
          <FormMessage />
        </FormItem>
      )}
    />
  </form>
</Form>
  • Form — the react-hook-form FormProvider.
  • FormField — binds one name / control and exposes field via render prop.
  • FormItem — scopes the ids that wire the parts together.
  • FormLabel — labels the control; turns destructive on error.
  • FormControl — threads aria-invalid / aria-describedby into the child input.
  • FormDescription — the helper line, referenced by aria-describedby.
  • FormMessage — the role='alert' validation line (renders nothing when valid).

Examples

Field types

FormField binds any control to the form. Wrap an Input, a Select, and a Checkbox in FormControl and each one inherits the field's id, error state, and aria-describedby — the whole input family composes through one wrapper.

● LIVE

Confirm clearance before transmitting.

API Reference

FormField

PropTypeDefaultDescription
namestringField name in the form values (react-hook-form).
controlControlThe control object from useForm().
rulesRegisterOptionsValidation rules (required, min, pattern, validate…).
render({ field, fieldState }) => ReactElementRender prop wiring field to your control.

Accessibility

  • FormControl threads aria-invalid and a context-aware aria-describedby into its child via Slot — description id always, description + message ids when there's an error.
  • FormItem scopes ids with useId, deriving formItemId, formDescriptionId, and formMessageId so label, control, description, and message all cross-reference.
  • FormLabel wires htmlFor to formItemId and flips to text-destructive-spark (via data-error) in lockstep with the field's error state.
  • FormMessage carries role='alert', so a validation error is announced live when it appears after a failed submit; it returns null when empty.
  • Error state propagates one aria-invalid to any okibi input through the single FormControl wrapper, flipping it to its destructive keyline.

Guidelines

Do

  • Wrap each control in FormControl so it inherits aria-invalid and aria-describedby automatically.
  • Put helper text in FormDescription and always include FormMessage to surface validation errors.
  • Define validation with the field rules (required, pattern, validate) so messages flow through automatically.

Don't

  • Don't hand-roll aria-describedby / aria-invalid on inputs inside a field — FormControl already wires them.
  • Don't render error text outside FormMessage; you'd lose the role='alert' live announcement.
  • Don't use FormField parts outside a Form provider — useFormField will throw.

On this page