{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "field",
  "type": "registry:component",
  "title": "Field",
  "description": "Groups a label, control, and hint or error in one vertical field.",
  "registryDependencies": [
    "https://vlak.dev/r/vlak-base.json",
    "https://vlak.dev/r/inter.json",
    "https://vlak.dev/r/input.json",
    "https://vlak.dev/r/vlak-lib.json"
  ],
  "dependencies": [
    "@stylexjs/stylex"
  ],
  "devDependencies": [
    "@stylexjs/babel-plugin"
  ],
  "docs": "Vlak leaves are StyleX. Compile them with @stylexjs/babel-plugin (Vite: @stylexjs/unplugin, Next: @stylexjs/nextjs-plugin). If you would rather not run a compiler, import @noorddev/vlak-react instead: it ships precompiled with one stylesheet.",
  "files": [
    {
      "path": "vlak/field.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak, mq } from \"./tokens.stylex\";\nimport { rs } from \"./rs\";\nimport { cx } from \"./cx\";\n\nexport interface FieldProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst styles = stylex.create({\n  field: {\n    width: \"100%\",\n    display: \"flex\",\n    flexDirection: \"column\",\n    gap: {\n      default: \"0.5rem\",\n      [mq.phone]: \"0.5rem\",\n    },\n  },\n  label: {\n    fontSize: {\n      default: \"0.75rem\",\n      [mq.phone]: vlak.controlLabel,\n    },\n    fontWeight: 600,\n    color: vlak.gray,\n    letterSpacing: \"-0.01em\",\n    lineHeight: \"16px\",\n  },\n  hint: {\n    margin: 0,\n    fontSize: {\n      default: \"0.75rem\",\n      [mq.phone]: \"0.875rem\",\n    },\n    fontWeight: 500,\n    color: vlak.gray,\n    letterSpacing: \"-0.01em\",\n    lineHeight: \"16px\",\n  },\n  error: {\n    margin: 0,\n    fontSize: {\n      default: \"0.75rem\",\n      [mq.phone]: \"0.875rem\",\n    },\n    fontWeight: 500,\n    color: vlak.ink,\n    letterSpacing: \"-0.01em\",\n    lineHeight: \"16px\",\n  },\n});\n\n/* ── Context: hint and error ids reach the control ── */\n\ntype FieldPart = \"hint\" | \"error\";\n\ninterface FieldContextValue {\n  hintId: string;\n  errorId: string;\n  /** Ids of the parts currently rendered. */\n  parts: { hint?: string; error?: string };\n  register: (part: FieldPart, id: string) => () => void;\n}\n\nexport const FieldContext = React.createContext<FieldContextValue | null>(null);\n\n/** Hint and error parts: the id to render, registered while mounted. */\nexport function useFieldPart(part: FieldPart, explicitId?: string): string | undefined {\n  const ctx = React.useContext(FieldContext);\n  const id = explicitId ?? (part === \"hint\" ? ctx?.hintId : ctx?.errorId);\n  // `register` is stable; the context value is not (it carries `parts`).\n  const register = ctx?.register;\n  React.useEffect(() => {\n    if (!register || !id) return;\n    return register(part, id);\n  }, [register, part, id]);\n  return id;\n}\n\nexport interface FieldControlAria {\n  \"aria-describedby\"?: string;\n  \"aria-invalid\"?: React.AriaAttributes[\"aria-invalid\"];\n}\n\n/**\n * Controls call this: the field's hint and error describe the control,\n * and a rendered error marks it invalid. Explicit props win.\n */\nexport function useFieldControl(props: FieldControlAria): FieldControlAria & { invalid: boolean } {\n  const ctx = React.useContext(FieldContext);\n  const describedBy = cx(props[\"aria-describedby\"], ctx?.parts.hint, ctx?.parts.error) || undefined;\n  const ariaInvalid = props[\"aria-invalid\"] ?? (ctx?.parts.error ? true : undefined);\n  return {\n    \"aria-describedby\": describedBy,\n    \"aria-invalid\": ariaInvalid,\n    invalid: ariaInvalid === true || ariaInvalid === \"true\",\n  };\n}\n\n/** Stack: label, control, hint or error. The hint and error describe the control. */\nexport const Field = React.forwardRef<HTMLDivElement, FieldProps>(function Field(\n  { className, style, ...props },\n  ref,\n) {\n  const base = React.useId();\n  const [parts, setParts] = React.useState<{ hint?: string; error?: string }>({});\n  const register = React.useCallback((part: FieldPart, id: string) => {\n    setParts((current) => (current[part] === id ? current : { ...current, [part]: id }));\n    return () => {\n      setParts((current) => (current[part] === id ? { ...current, [part]: undefined } : current));\n    };\n  }, []);\n  const value = React.useMemo<FieldContextValue>(\n    () => ({ hintId: `${base}-hint`, errorId: `${base}-error`, parts, register }),\n    [base, parts, register],\n  );\n  const sx = rs([\"rs-field\", className], styles.field);\n  return (\n    <FieldContext.Provider value={value}>\n      <div ref={ref} {...props} className={sx.className} style={{ ...sx.style, ...style }} />\n    </FieldContext.Provider>\n  );\n});\n\nexport const FieldLabel = React.forwardRef<HTMLLabelElement, React.LabelHTMLAttributes<HTMLLabelElement>>(function FieldLabel(\n  { className, style, ...props },\n  ref,\n) {\n  const sx = rs([\"rs-field-label\", className], styles.label);\n  return <label ref={ref} {...props} className={sx.className} style={{ ...sx.style, ...style }} />;\n});\n\nexport const FieldHint = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(function FieldHint(\n  { className, style, id, ...props },\n  ref,\n) {\n  const hintId = useFieldPart(\"hint\", id);\n  const sx = rs([\"rs-field-hint\", className], styles.hint);\n  return <p ref={ref} {...props} id={hintId} className={sx.className} style={{ ...sx.style, ...style }} />;\n});\n\nexport const FieldError = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(function FieldError(\n  { className, style, id, ...props },\n  ref,\n) {\n  const errorId = useFieldPart(\"error\", id);\n  const sx = rs([\"rs-field-error\", className], styles.error);\n  return <p ref={ref} role=\"alert\" {...props} id={errorId} className={sx.className} style={{ ...sx.style, ...style }} />;\n});\n",
      "type": "registry:component",
      "target": "components/vlak/field.tsx"
    },
    {
      "path": "vlak/styles/field.css",
      "content": "/* ── field: generated from packages/react/src/components/field.tsx ── */\n.rs-field{width:100%;display:flex;flex-direction:column;gap:0.5rem}\n@media (max-width: 640px){.rs-field{gap:0.5rem}}\n.rs-field-label{font-size:0.75rem;font-weight:600;color:var(--text-secondary);letter-spacing:-0.01em;line-height:16px}\n@media (max-width: 640px){.rs-field-label{font-size:var(--control-label)}}\n.rs-field-hint{margin:0;font-size:0.75rem;font-weight:500;color:var(--text-secondary);letter-spacing:-0.01em;line-height:16px}\n@media (max-width: 640px){.rs-field-hint{font-size:0.875rem}}\n.rs-field-error{margin:0;font-size:0.75rem;font-weight:500;color:var(--text);letter-spacing:-0.01em;line-height:16px}\n@media (max-width: 640px){.rs-field-error{font-size:0.875rem}}\n",
      "type": "registry:file",
      "target": "styles/vlak/field.css"
    }
  ],
  "meta": {
    "vlak": {
      "category": "forms",
      "classes": [
        "rs-field",
        "rs-field-label",
        "rs-field-hint",
        "rs-field-error"
      ],
      "snippet": "<div class=\"rs-field\"><label class=\"rs-field-label\" for=\"invoice-name\">Name</label><input class=\"rs-input rs-input-full\" id=\"invoice-name\" aria-describedby=\"invoice-name-hint\" /><p class=\"rs-field-hint\" id=\"invoice-name-hint\">As it appears on the invoice.</p></div>",
      "cssOnly": false,
      "registryDependencies": [
        "input",
        "vlak-lib"
      ],
      "aliases": [
        "Field",
        "Form field",
        "Form item",
        "FormField",
        "Form control"
      ],
      "example": "import { Field, FieldError, FieldHint, FieldLabel, Input } from \"@noorddev/vlak-react\";\n\n<Field>\n  <FieldLabel htmlFor=\"name\">Name</FieldLabel>\n  <Input plain id=\"name\" />\n  <FieldHint>As it appears on the invoice.</FieldHint>\n</Field>\n\n<Field>\n  <FieldLabel htmlFor=\"iban\">IBAN</FieldLabel>\n  <Input plain id=\"iban\" />\n  <FieldError>Check the country code.</FieldError>\n</Field>",
      "usage": {
        "use": [
          "Any control with a label plus a hint or an error, including NativeSelect, Textarea, Slider, and InputOTP.",
          "Form layouts where every field stacks the same way."
        ],
        "avoid": [
          "A lone Input; its label, hint, and error props do the same job.",
          "Grouping several controls; use a fieldset with a legend."
        ]
      },
      "a11y": [
        "Field passes the ids of a rendered FieldHint and FieldError to the control through aria-describedby.",
        "A rendered FieldError sets aria-invalid on the control and has role=\"alert\".",
        "FieldLabel is a native <label>; pass htmlFor with the control's id."
      ]
    }
  }
}
