{
  "schemaVersion": 1,
  "name": "form",
  "useFor": "Use for any form with validation. Create the form with useForm, wrap it in Form, and give each control a FormField with FormItem, FormLabel, FormControl around the control, and FormDescription or FormMessage under it. Validation rules go in FormField's rules or a resolver.",
  "constraints": [
    "Red labels on invalid fields",
    "Validating on every keystroke before the first submit",
    "Hiding the error message in a tooltip"
  ],
  "documentation": "",
  "source": "Extrapolated from opencommerce (the Field: label, control, hint)",
  "exports": [
    "Form",
    "FormControl",
    "FormDescription",
    "FormField",
    "FormItem",
    "FormLabel",
    "FormMessage",
    "useFormField"
  ],
  "client": true,
  "docsUrl": "https://opencommerce-ds.vercel.app/components/form",
  "markdownUrl": "https://opencommerce-ds.vercel.app/r/form.md",
  "examples": [
    {
      "path": "examples/form.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useForm } from \"react-hook-form\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from \"@/components/ui/form\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { RadioGroup, RadioGroupItem } from \"@/components/ui/radio-group\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\nimport { Spinner } from \"@/components/ui/spinner\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Textarea } from \"@/components/ui/textarea\";\n\ntype Values = { name: string; budget: string; store: string; mode: string; notes: string; receipts: boolean; terms: boolean };\n\nexport function Site() {\n  const form = useForm<Values>({ defaultValues: { name: \"\", budget: \"\", store: \"\", mode: \"confirm\", notes: \"\", receipts: true, terms: false } });\n  const [state, setState] = React.useState<\"idle\" | \"saving\" | \"saved\">(\"idle\");\n  const onSubmit = form.handleSubmit(async () => {\n    setState(\"saving\");\n    await new Promise((r) => setTimeout(r, 900));\n    setState(\"saved\");\n  });\n  return (\n    <Form {...form}>\n      <form onSubmit={onSubmit} noValidate className=\"grid w-full max-w-110 gap-4\">\n        <FormField\n          control={form.control}\n          name=\"name\"\n          rules={{ required: \"Name the agent.\", pattern: { value: /^[a-z0-9-]+$/, message: \"Use lowercase letters, numbers and dashes.\" } }}\n          render={({ field }) => (\n            <FormItem>\n              <FormLabel>Agent name</FormLabel>\n              <FormControl>\n                <Input placeholder=\"office-supplies\" autoComplete=\"off\" {...field} />\n              </FormControl>\n              <FormDescription>Shown on every order this agent places.</FormDescription>\n              <FormMessage />\n            </FormItem>\n          )}\n        />\n        <FormField\n          control={form.control}\n          name=\"budget\"\n          rules={{ required: \"Set a per-order budget.\", validate: (v) => Number(v) > 0 || \"Enter an amount above zero.\" }}\n          render={({ field }) => (\n            <FormItem>\n              <FormLabel>Per-order budget (USD)</FormLabel>\n              <FormControl>\n                <Input inputMode=\"decimal\" placeholder=\"200\" {...field} />\n              </FormControl>\n              <FormMessage />\n            </FormItem>\n          )}\n        />\n        <FormField\n          control={form.control}\n          name=\"store\"\n          rules={{ required: \"Choose a default store.\" }}\n          render={({ field }) => (\n            <FormItem>\n              <FormLabel>Default store</FormLabel>\n              <Select value={field.value} onValueChange={field.onChange} name={field.name}>\n                <FormControl>\n                  <SelectTrigger>\n                    <SelectValue placeholder=\"Choose a store\" />\n                  </SelectTrigger>\n                </FormControl>\n                <SelectContent>\n                  <SelectItem value=\"amazon\">Amazon</SelectItem>\n                  <SelectItem value=\"staples\">Staples</SelectItem>\n                  <SelectItem value=\"wayfair\">Wayfair</SelectItem>\n                </SelectContent>\n              </Select>\n              <FormMessage />\n            </FormItem>\n          )}\n        />\n        <FormField\n          control={form.control}\n          name=\"mode\"\n          render={({ field }) => (\n            <FormItem>\n              <FormLabel>Checkout</FormLabel>\n              <FormControl>\n                <RadioGroup value={field.value} onValueChange={field.onChange} name={field.name}>\n                  <div className=\"flex items-center gap-2.5\">\n                    <RadioGroupItem value=\"auto\" id=\"mode-auto\" />\n                    <Label variant=\"control\" htmlFor=\"mode-auto\">Buy automatically</Label>\n                  </div>\n                  <div className=\"flex items-center gap-2.5\">\n                    <RadioGroupItem value=\"confirm\" id=\"mode-confirm\" />\n                    <Label variant=\"control\" htmlFor=\"mode-confirm\">Ask before buying</Label>\n                  </div>\n                </RadioGroup>\n              </FormControl>\n            </FormItem>\n          )}\n        />\n        <FormField\n          control={form.control}\n          name=\"notes\"\n          rules={{ maxLength: { value: 200, message: \"Keep notes under 200 characters.\" } }}\n          render={({ field }) => (\n            <FormItem>\n              <FormLabel>Notes for the agent</FormLabel>\n              <FormControl>\n                <Textarea placeholder=\"Prefer recycled paper.\" {...field} />\n              </FormControl>\n              <FormMessage />\n            </FormItem>\n          )}\n        />\n        <FormField\n          control={form.control}\n          name=\"receipts\"\n          render={({ field }) => (\n            <FormItem className=\"flex-row items-center justify-between gap-4\">\n              <div className=\"grid gap-0.5\">\n                <FormLabel>Email receipts</FormLabel>\n                <FormDescription>Send each receipt to the finance inbox.</FormDescription>\n              </div>\n              <FormControl>\n                <Switch checked={field.value} onCheckedChange={field.onChange} />\n              </FormControl>\n            </FormItem>\n          )}\n        />\n        <FormField\n          control={form.control}\n          name=\"terms\"\n          rules={{ validate: (v) => v || \"Accept the spending policy to continue.\" }}\n          render={({ field }) => (\n            <FormItem>\n              <div className=\"flex items-center gap-2.5\">\n                <FormControl>\n                  <Checkbox checked={field.value} onCheckedChange={(v) => field.onChange(v === true)} />\n                </FormControl>\n                <FormLabel className=\"font-sans text-body-sm text-fg\">I accept the spending policy</FormLabel>\n              </div>\n              <FormMessage />\n            </FormItem>\n          )}\n        />\n        <div className=\"flex items-center gap-3\">\n          <Button type=\"submit\" disabled={state === \"saving\"}>\n            {state === \"saving\" && <Spinner />}\n            {state === \"saving\" ? \"Creating agent\" : \"Create agent\"}\n          </Button>\n          <Button type=\"button\" variant=\"secondary\" onClick={() => { form.reset(); setState(\"idle\"); }}>\n            Reset\n          </Button>\n        </div>\n        <p role=\"status\" className=\"font-sans text-body-sm text-fg-muted\">\n          {state === \"saved\" ? \"Agent created. It can place orders now.\" : \"\"}\n        </p>\n      </form>\n    </Form>\n  );\n}\n",
      "exports": [
        "Site"
      ],
      "requires": [
        "form",
        "button",
        "checkbox",
        "input",
        "label",
        "radio-group",
        "select",
        "spinner",
        "switch",
        "textarea"
      ],
      "packages": [
        "react-hook-form"
      ],
      "previews": [
        {
          "name": "Site",
          "url": "https://opencommerce-ds.vercel.app/preview/form/Site"
        }
      ]
    }
  ],
  "type": "ui",
  "title": "Form",
  "description": "react-hook-form wired to the brand's field layout: a 12px muted label, the control, a hint or a red message.",
  "surface": "site",
  "kinds": [],
  "requires": [
    "theme",
    "label",
    "utils"
  ],
  "packages": [
    "@radix-ui/react-slot",
    "react-hook-form"
  ],
  "files": [
    {
      "path": "components/ui/form.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { Controller, FormProvider, useFormContext, useFormState, type ControllerProps, type FieldPath, type FieldValues } from \"react-hook-form\";\n\nimport { Label } from \"@/components/ui/label\";\nimport { cn } from \"@/lib/utils\";\n\n// A react-hook-form form laid out as the brand's Field: each FormItem stacks its 12px Inter label at 64 percent ink, the\n// control 6px below it, then a 14px line under the control: a FormDescription at 64 percent ink, or the FormMessage in\n// the brand's red when the value is rejected. A field that fails keeps its label as it is: only the control (aria-invalid,\n// the red edge) and the message turn red. Fields sit 16px apart in a form.\nconst Form = FormProvider;\n\ntype FormFieldContextValue<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>> = { name: TName };\n\nconst FormFieldContext = React.createContext<FormFieldContextValue | null>(null);\n\nfunction FormField<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>(props: ControllerProps<TFieldValues, TName>) {\n  return (\n    <FormFieldContext.Provider value={{ name: props.name }}>\n      <Controller {...props} />\n    </FormFieldContext.Provider>\n  );\n}\n\ntype FormItemContextValue = { id: string };\n\nconst FormItemContext = React.createContext<FormItemContextValue | null>(null);\n\n/** The state of the field around it: its ids for labels and messages, and react-hook-form's error, dirty and touched state. */\nfunction useFormField() {\n  const fieldContext = React.useContext(FormFieldContext);\n  const itemContext = React.useContext(FormItemContext);\n  const { getFieldState } = useFormContext();\n  if (!fieldContext) throw new Error(\"useFormField should be used within <FormField>\");\n  if (!itemContext) throw new Error(\"useFormField should be used within <FormItem>\");\n  const formState = useFormState({ name: fieldContext.name });\n  const fieldState = getFieldState(fieldContext.name, formState);\n  const { id } = itemContext;\n  return { id, name: fieldContext.name, formItemId: `${id}-form-item`, formDescriptionId: `${id}-form-item-description`, formMessageId: `${id}-form-item-message`, ...fieldState };\n}\n\nfunction FormItem({ className, ...props }: React.ComponentProps<\"div\">) {\n  const id = React.useId();\n  return (\n    <FormItemContext.Provider value={{ id }}>\n      <div data-slot=\"form-item\" className={cn(\"flex min-w-0 flex-col gap-1.5\", className)} {...props} />\n    </FormItemContext.Provider>\n  );\n}\n\nfunction FormLabel({ className, ...props }: React.ComponentProps<typeof Label>) {\n  const { error, formItemId } = useFormField();\n  return <Label data-slot=\"form-label\" data-error={!!error} htmlFor={formItemId} className={cn(\"w-fit\", className)} {...props} />;\n}\n\n/** Hands the field's id, its description and message ids and aria-invalid to the one control inside it. */\nfunction FormControl(props: React.ComponentProps<typeof Slot>) {\n  const { error, formItemId, formDescriptionId, formMessageId } = useFormField();\n  return <Slot data-slot=\"form-control\" id={formItemId} aria-describedby={!error ? formDescriptionId : `${formDescriptionId} ${formMessageId}`} aria-invalid={!!error} {...props} />;\n}\n\nfunction FormDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n  const { formDescriptionId } = useFormField();\n  return <p data-slot=\"form-description\" id={formDescriptionId} className={cn(\"m-0 font-sans text-body-sm leading-snug text-fg-muted\", className)} {...props} />;\n}\n\nfunction FormMessage({ className, children, ...props }: React.ComponentProps<\"p\">) {\n  const { error, formMessageId } = useFormField();\n  const body = error ? String(error.message ?? \"\") : children;\n  if (!body) return null;\n  return (\n    <p data-slot=\"form-message\" id={formMessageId} role={error ? \"alert\" : undefined} className={cn(\"m-0 font-sans text-body-sm leading-snug text-danger\", className)} {...props}>\n      {body}\n    </p>\n  );\n}\n\nexport { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };\n"
    }
  ]
}
