{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "field",
  "type": "registry:ui",
  "title": "Field",
  "description": "Form-field wrapper bundling label, control, description, and error message with correct a11y wiring.",
  "dependencies": [
    "@saasflare/ui",
    "class-variance-authority"
  ],
  "files": [
    {
      "path": "components/ui/field.tsx",
      "type": "registry:ui",
      "content": "// @toreview\n\"use client\"\n\n/**\n * @fileoverview Field primitive — structured form field layout with label, description,\n * error message, and fieldset/legend support. Provides consistent spacing and\n * accessibility for form inputs. Part of the Saasflare base component layer.\n * @module packages/ui/components/ui/field\n * @layer core\n *\n * @component\n * @example\n * import { Field, FieldLabel, FieldError } from '@saasflare/ui';\n * <Field>\n *   <FieldLabel>Email</FieldLabel>\n *   <input type=\"email\" />\n *   <FieldError>Required</FieldError>\n * </Field>\n */\n\nimport * as React from \"react\"\nimport { useMemo } from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@saasflare/ui\"\nimport { useSaasflareProps, type SaasflareComponentProps } from \"@saasflare/ui\"\nimport { Label } from \"@saasflare/ui\"\nimport { Separator } from \"@saasflare/ui\"\n\n/**\n * Fieldset wrapper that groups related fields with consistent vertical spacing.\n *\n * @component\n * @layer core\n */\nfunction FieldSet({ className, ...props }: React.ComponentProps<\"fieldset\">) {\n  return (\n    <fieldset\n      data-slot=\"field-set\"\n      className={cn(\n        \"flex flex-col gap-6\",\n        \"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/**\n * Legend heading for a {@link FieldSet}, with `legend` (default) and `label` size variants.\n *\n * @component\n * @layer core\n */\nfunction FieldLegend({\n  className,\n  variant = \"legend\",\n  ...props\n}: React.ComponentProps<\"legend\"> & { variant?: \"legend\" | \"label\" }) {\n  return (\n    <legend\n      data-slot=\"field-legend\"\n      data-variant={variant}\n      className={cn(\n        \"mb-3 font-medium\",\n        \"data-[variant=legend]:text-base\",\n        \"data-[variant=label]:text-sm\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/**\n * Container that stacks multiple {@link Field} elements with responsive container queries.\n *\n * @component\n * @layer core\n */\nfunction FieldGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"field-group\"\n      className={cn(\n        \"group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nconst fieldVariants = cva(\n  \"group/field flex w-full gap-3 data-[invalid=true]:text-destructive\",\n  {\n    variants: {\n      orientation: {\n        vertical: [\"flex-col [&>*]:w-full [&>.sr-only]:w-auto\"],\n        horizontal: [\n          \"flex-row items-center\",\n          \"[&>[data-slot=field-label]]:flex-auto\",\n          \"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\n        ],\n        responsive: [\n          \"flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto\",\n          \"@md/field-group:[&>[data-slot=field-label]]:flex-auto\",\n          \"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\n        ],\n      },\n    },\n    defaultVariants: {\n      orientation: \"vertical\",\n    },\n  }\n)\n\ninterface FieldProps\n  extends Omit<React.ComponentProps<\"div\">, keyof SaasflareComponentProps>,\n    VariantProps<typeof fieldVariants>,\n    SaasflareComponentProps {}\n\n/**\n * Structured form field root — lays out a label, control, description and error\n * with consistent spacing. Honors the Saasflare theme axes (surface, radius,\n * animated) so nested selectable fields adopt the active design tokens.\n *\n * @component\n * @layer core\n *\n * @example\n * <Field orientation=\"horizontal\">\n *   <FieldLabel>Notifications</FieldLabel>\n *   <FieldDescription>Receive product updates</FieldDescription>\n * </Field>\n */\nfunction Field({\n  className,\n  orientation = \"vertical\",\n  surface,\n  radius,\n  animated,\n  iconWeight,\n  ...props\n}: FieldProps) {\n  const sf = useSaasflareProps({ surface, radius, animated, iconWeight })\n\n  return (\n    <div\n      role=\"group\"\n      data-slot=\"field\"\n      data-orientation={orientation}\n      data-surface={sf.surface}\n      data-radius={sf.radius}\n      data-animated={String(sf.animated)}\n      className={cn(fieldVariants({ orientation }), className)}\n      {...props}\n    />\n  )\n}\n\n/**\n * Vertical content column inside a {@link Field}, typically wrapping a label and description.\n *\n * @component\n * @layer core\n */\nfunction FieldContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"field-content\"\n      className={cn(\n        \"group/field-content flex flex-1 flex-col gap-1.5 leading-snug\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/**\n * Label for a {@link Field}. When wrapping a nested field it renders as a\n * selectable bordered card that highlights on the checked state.\n *\n * @component\n * @layer core\n */\nfunction FieldLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof Label>) {\n  return (\n    <Label\n      data-slot=\"field-label\"\n      className={cn(\n        \"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50\",\n        \"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4\",\n        \"has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5 dark:has-data-[state=checked]:bg-primary/10\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/**\n * Inline title text for a field, used where a full {@link FieldLabel} is not needed.\n *\n * @component\n * @layer core\n */\nfunction FieldTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"field-label\"\n      className={cn(\n        \"flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/**\n * Helper / description text rendered beneath a field control.\n *\n * @component\n * @layer core\n */\nfunction FieldDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n  return (\n    <p\n      data-slot=\"field-description\"\n      className={cn(\n        \"text-sm leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance\",\n        \"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5\",\n        \"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/**\n * Horizontal divider between fields, with optional centered label content.\n *\n * @component\n * @layer core\n */\nfunction FieldSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  children?: React.ReactNode\n}) {\n  return (\n    <div\n      data-slot=\"field-separator\"\n      data-content={!!children}\n      className={cn(\n        \"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2\",\n        className\n      )}\n      {...props}\n    >\n      <Separator className=\"absolute inset-0 top-1/2\" />\n      {children && (\n        <span\n          className=\"relative mx-auto block w-fit bg-background px-2 text-muted-foreground\"\n          data-slot=\"field-separator-content\"\n        >\n          {children}\n        </span>\n      )}\n    </div>\n  )\n}\n\n/**\n * Validation error region for a field. Renders `children` when provided, otherwise\n * a deduplicated list of `errors` (single message inline, multiple as a bullet list).\n *\n * @component\n * @layer core\n */\nfunction FieldError({\n  className,\n  children,\n  errors,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  errors?: Array<{ message?: string } | undefined>\n}) {\n  const content = useMemo(() => {\n    if (children) {\n      return children\n    }\n\n    if (!errors?.length) {\n      return null\n    }\n\n    const uniqueErrors = [\n      ...new Map(errors.map((error) => [error?.message, error])).values(),\n    ]\n\n    if (uniqueErrors?.length === 1) {\n      return uniqueErrors[0]?.message\n    }\n\n    return (\n      <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n        {uniqueErrors.map(\n          (error) =>\n            error?.message && <li key={error.message}>{error.message}</li>\n        )}\n      </ul>\n    )\n  }, [children, errors])\n\n  if (!content) {\n    return null\n  }\n\n  return (\n    <div\n      role=\"alert\"\n      data-slot=\"field-error\"\n      className={cn(\"text-sm font-normal text-destructive\", className)}\n      {...props}\n    >\n      {content}\n    </div>\n  )\n}\n\nexport {\n  Field,\n  FieldLabel,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLegend,\n  FieldSeparator,\n  FieldSet,\n  FieldContent,\n  FieldTitle,\n}\n",
      "target": "components/ui/field.tsx"
    }
  ],
  "$meta": {
    "source": "https://ui.saasflare.io/r/field.json"
  }
}
