{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "form",
  "title": "Form",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "React Hook Form wired to Ballmac Field: one FormField renders label, control, description and error with ids, aria-invalid and aria-describedby linked, plus a pending submit button.",
  "dependencies": [
    "react-hook-form@^7"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/button.json",
    "https://ui.ballmac.com/r/field.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/form.tsx",
      "content": "// Ballmac UI: Form. https://ui.ballmac.com/components/form\n// Based on shadcn/ui Form (MIT, Copyright (c) 2023 shadcn) on React Hook Form (MIT, Copyright (c) 2019 Beier Luo), rebuilt on Ballmac Field so one FormField renders label, control, description and error with ids, aria-invalid and aria-describedby wired for you, and a submit button with a pending state.\n\"use client\";\n\nimport * as React from \"react\";\nimport {\n  Controller,\n  FormProvider,\n  useFormContext,\n  useFormState,\n  type ControllerProps,\n  type ControllerRenderProps,\n  type FieldPath,\n  type FieldValues,\n  type SubmitErrorHandler,\n  type SubmitHandler,\n  type UseFormReturn,\n} from \"react-hook-form\";\nimport { Button, type ButtonProps } from \"@/components/ballmac/button\";\nimport {\n  Field,\n  FieldDescription,\n  FieldError,\n  FieldLabel,\n  useFieldControl,\n  type FieldProps,\n} from \"@/components/ballmac/field\";\nimport { cn } from \"@/lib/utils\";\n\ntype FormProps<TValues extends FieldValues> = Omit<React.ComponentProps<\"form\">, \"onSubmit\" | \"onError\"> & {\n  /** The object returned by `useForm()`. */\n  form: UseFormReturn<TValues>;\n  /** Called with validated values when the form is submitted. */\n  onSubmit: SubmitHandler<TValues>;\n  /** Called with the errors when validation fails. By default the first invalid control is focused. */\n  onError?: SubmitErrorHandler<TValues>;\n};\n\n/** Provides the form to every FormField and wires `<form>` to `handleSubmit`. Browser validation is off (`noValidate`) so your schema owns the messages. */\nfunction Form<TValues extends FieldValues>({ form, onSubmit, onError, className, children, ...props }: FormProps<TValues>) {\n  return (\n    <FormProvider {...form}>\n      <form\n        data-slot=\"form\"\n        noValidate\n        onSubmit={form.handleSubmit(onSubmit, onError)}\n        className={cn(\"grid w-full gap-5\", className)}\n        {...props}\n      >\n        {children}\n      </form>\n    </FormProvider>\n  );\n}\n\n/** Everything a control needs, ready to spread: `<Input {...props} />`. */\ntype FormControlProps<TValues extends FieldValues = FieldValues, TName extends FieldPath<TValues> = FieldPath<TValues>> =\n  ControllerRenderProps<TValues, TName> & {\n    id?: string;\n    \"aria-invalid\"?: true;\n    \"aria-describedby\"?: string;\n  };\n\ntype FormFieldProps<TValues extends FieldValues, TName extends FieldPath<TValues>> = {\n  /** Field name, typed from your form values (supports paths such as `address.city`). */\n  name: TName;\n  /** Visible label. */\n  label: React.ReactNode;\n  /** Help text under the control. It stays readable to screen readers when an error is shown. */\n  description?: React.ReactNode;\n  /** Show the required asterisk. Pair it with a schema rule. */\n  required?: boolean;\n  /** Label and control layout. `horizontal` suits checkboxes and switches. */\n  orientation?: FieldProps[\"orientation\"];\n  /** Built-in validation rules (`required`, `minLength`, `pattern`, `validate`…). Not needed when you use a schema resolver. */\n  rules?: ControllerProps<TValues, TName>[\"rules\"];\n  /** Optional form object; defaults to the nearest `<Form>`. */\n  form?: UseFormReturn<TValues>;\n  /** Renders the control. Spread the props onto it: `render={(props) => <Input {...props} />}`. For checkboxes or switches map `props.value` to `checked` and `props.onChange` to `onCheckedChange`. */\n  render: (props: FormControlProps<TValues, TName>) => React.ReactNode;\n  className?: string;\n};\n\nfunction ControlHost<TValues extends FieldValues, TName extends FieldPath<TValues>>({\n  field,\n  render,\n}: {\n  field: ControllerRenderProps<TValues, TName>;\n  render: FormFieldProps<TValues, TName>[\"render\"];\n}) {\n  const control = useFieldControl();\n  return <>{render({ ...field, ...control } as FormControlProps<TValues, TName>)}</>;\n}\n\n/** One field: label, your control, description and the validation message, all linked. */\nfunction FormField<TValues extends FieldValues, TName extends FieldPath<TValues>>({\n  name,\n  label,\n  description,\n  required,\n  orientation,\n  form,\n  rules,\n  render,\n  className,\n}: FormFieldProps<TValues, TName>) {\n  const context = useFormContext<TValues>();\n  const control = (form ?? context).control;\n  const horizontal = orientation === \"horizontal\";\n  return (\n    <Controller\n      control={control}\n      name={name}\n      rules={rules}\n      render={({ field, fieldState }) => (\n        <Field data-slot=\"form-field\" invalid={fieldState.invalid} orientation={orientation} className={className}>\n          {horizontal ? (\n            <>\n              <ControlHost field={field} render={render} />\n              <div className=\"flex flex-1 flex-col gap-1\">\n                <FieldLabel required={required}>{label}</FieldLabel>\n                {description && <FieldDescription>{description}</FieldDescription>}\n                <FieldError errors={[fieldState.error]} />\n              </div>\n            </>\n          ) : (\n            <>\n              <FieldLabel required={required}>{label}</FieldLabel>\n              <ControlHost field={field} render={render} />\n              {description && <FieldDescription>{description}</FieldDescription>}\n              <FieldError errors={[fieldState.error]} />\n            </>\n          )}\n        </Field>\n      )}\n    />\n  );\n}\n\ntype FormSubmitProps = Omit<ButtonProps, \"type\" | \"loading\"> & {\n  /** Text shown on the button while the submit handler is running. */\n  pendingText?: React.ReactNode;\n};\n/** Submit button that shows a spinner and blocks repeat clicks while `onSubmit` is pending. */\nfunction FormSubmit({ children, pendingText, disabled, ...props }: FormSubmitProps) {\n  const { isSubmitting } = useFormState();\n  return (\n    <Button type=\"submit\" loading={isSubmitting} disabled={disabled} {...props}>\n      {isSubmitting && pendingText ? pendingText : children}\n    </Button>\n  );\n}\n\nexport {\n  Form,\n  FormField,\n  FormSubmit,\n  useFormContext,\n  type FormProps,\n  type FormFieldProps,\n  type FormControlProps,\n  type FormSubmitProps,\n};\n",
      "type": "registry:ui",
      "target": "@components/ballmac/form.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "form",
      "validation",
      "react-hook-form",
      "field"
    ],
    "version": "1.0.0",
    "updated": "2026-09-30",
    "ai": {
      "summary": "const form = useForm(); <Form form onSubmit><FormField name label render={(props) => <Input {...props}/>}/><FormSubmit/></Form>. Use rules or a resolver for validation.",
      "whenToUse": [
        "Any form with client validation",
        "Forms that need per-field errors announced"
      ],
      "whenNotToUse": [
        "A single uncontrolled input; use field",
        "Server actions without client state"
      ],
      "composesWith": [
        "field",
        "input",
        "checkbox",
        "select"
      ],
      "a11y": [
        {
          "keys": "Enter",
          "action": "Submits; focus moves to the first invalid control"
        },
        {
          "keys": "Screen readers",
          "action": "Labels, hints and errors are linked; errors use role alert"
        }
      ],
      "customization": [
        "rules or resolver",
        "orientation horizontal for checkboxes",
        "pendingText on FormSubmit",
        "description and required"
      ]
    },
    "source": {
      "name": "shadcn/ui Form",
      "url": "https://github.com/shadcn-ui/ui",
      "license": "MIT",
      "copyright": "Copyright (c) 2023 shadcn",
      "modified": true
    },
    "examples": [
      "form-demo",
      "form-states"
    ],
    "url": "https://ui.ballmac.com/components/form"
  },
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}