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-fieldWithout 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>
)
}