jengacore.shadcn registry · Tailwind v4 · Radix and Base UI
Components
jengacore components · @jengacore/form-field

FormField

Label, control and helper or error that says how to fix it; required asterisk and ARIA wiring.

USD

Alert at 80% of the cap.

Enter 12 digits including country code.

Install

terminal
npx shadcn@latest add @jengacore/form-field

Without the namespace configured: npx shadcn@latest add https://ui.jengacore.com/r/<new-york-v4|base-nova>/form-field.json

Registry dependencies

npm packages

lucide-react

Source

components/jc/form-field.tsx
import * as React from "react"
import { CircleAlert } from "lucide-react"

import { cn } from "@/lib/utils"

/**
 * Label + control + helper or error: the building block of every form.
 * Label above the control in sentence case; required gets a red asterisk; the error says how to fix it
 * ("Enter 12 digits including country code"), never just "Invalid". Pass the control as the child and give
 * it the same `id` as `htmlFor`; aria-invalid and aria-describedby are wired for you.
 */
export function FormField({
  label,
  htmlFor,
  required,
  helper,
  error,
  className,
  children,
}: {
  label: React.ReactNode
  htmlFor: string
  required?: boolean
  helper?: React.ReactNode
  error?: React.ReactNode
  className?: string
  children: React.ReactElement<{ "aria-invalid"?: boolean; "aria-describedby"?: string; required?: boolean }>
}) {
  const describedBy = error ? `${htmlFor}-error` : helper ? `${htmlFor}-helper` : undefined
  const control = React.cloneElement(children, {
    "aria-invalid": error ? true : undefined,
    "aria-describedby": describedBy,
    required,
  })
  return (
    <div data-slot="form-field" className={cn("grid gap-1.5", className)}>
      <label htmlFor={htmlFor} className="text-xs leading-4 font-medium">
        {label}
        {required ? (
          <span className="ml-0.5 text-destructive" aria-hidden>
            *
          </span>
        ) : null}
      </label>
      {control}
      {error ? (
        <p id={`${htmlFor}-error`} className="flex items-start gap-1.5 text-[13px] leading-[18px] text-destructive">
          <CircleAlert className="mt-px size-3.5 shrink-0 stroke-2" aria-hidden />
          {error}
        </p>
      ) : helper ? (
        <p id={`${htmlFor}-helper`} className="text-[13px] leading-[18px] text-muted-foreground">
          {helper}
        </p>
      ) : null}
    </div>
  )
}