C
Cassa UI

@cassa/ui-forms

Form primitives wired to React Hook Form and Zod. Every field handles validation state, error messages, and accessibility out of the box.

Import

import {
  Input,
  Textarea,
  Select,
  Checkbox,
  Switch,
  FormField,
  FormInput,
  FormTextarea,
  FormSelect,
  FormCheckbox,
  FormSwitch,
} from '@cassa/ui-forms'

Uncontrolled primitives

Use Input, Textarea, Select, etc. standalone — they accept native HTML attributes and forward refs.

<Input placeholder="Email" type="email" />
<Input placeholder="Error state" error />
<Textarea placeholder="Description" rows={4} />
<Select options={[{ value: 'admin', label: 'Admin' }, { value: 'user', label: 'User' }]} />
<Checkbox label="Accept terms" />
<Switch label="Enable notifications" />

Wired to React Hook Form

The FormInput family wraps each primitive with FormField, binding it to RHF and showing Zod errors automatically.

'use client'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { FormInput, FormSelect, FormCheckbox } from '@cassa/ui-forms'
import { Button } from '@cassa/ui'

const schema = z.object({
  email: z.string().email('Valid email required'),
  role: z.string().min(1, 'Select a role'),
  terms: z.boolean().refine(v => v, 'You must accept the terms'),
})

export const MyForm = () => {
  const { control, handleSubmit } = useForm({
    resolver: zodResolver(schema),
    defaultValues: { email: '', role: '', terms: false },
  })

  return (
    <form onSubmit={handleSubmit(console.log)} className="space-y-4">
      <FormInput
        control={control}
        name="email"
        label="Email"
        type="email"
        placeholder="you@example.com"
      />
      <FormSelect
        control={control}
        name="role"
        label="Role"
        options={[
          { value: 'admin', label: 'Admin' },
          { value: 'user', label: 'User' },
        ]}
      />
      <FormCheckbox
        control={control}
        name="terms"
        label="I accept the terms and conditions"
      />
      <Button type="submit">Submit</Button>
    </form>
  )
}

FormField (low-level)

For custom inputs, use FormField directly with a render prop:

<FormField
  control={control}
  name="bio"
  label="Bio"
  description="Tell us a bit about yourself."
  render={({ field }) => <Textarea {...field} rows={4} />}
/>
PropTypeDefaultDescription
name*stringField name — must match a key in the Zod schema.
label*stringVisible label text.
descriptionstringHelper text shown below the field.
control*ControlReact Hook Form control object.
render*(field) => ReactNodeRender prop receiving the bound field.

Input props

PropTypeDefaultDescription
typestring"text"HTML input type.
placeholderstringPlaceholder text.
disabledbooleanfalseDisables the field.
errorbooleanfalseApplies error ring styling.
...restInputHTMLAttributesAll native input attributes are forwarded.