Field
A visible, clickable label above the control and a hint below that names the fix when there is an error.
Add the domain after the @, for example ana@studio.com.
Installation
$ pnpm dlx shadcn@latest add @ssntl/fieldUsage
import { Field, FieldHint, FieldLabel } from "@/components/ui/field"
<Field>
<FieldLabel htmlFor="email">Work email</FieldLabel>
<Input id="email" type="email" aria-invalid aria-describedby="email-hint" />
<FieldHint id="email-hint" error>Add the domain after the @.</FieldHint>
</Field>- Clickable labels: FieldLabel is a real <label>; htmlFor ties it to the control.
- The label stays visible above the control; a placeholder never replaces it.
- An error sets aria-invalid on the control, points to the hint with aria-describedby and names the fix.
- The hint is caption size in muted text; destructive only for errors.
Code
import * as React from "react"
import { cn } from "@/lib/utils"
// Essential Design `es-field`: a visible label above, the control, an optional
// hint below. The label is a real <label> (clicking it focuses the control);
// placeholders never replace it. An error hint says how to fix the problem.
function Field({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field"
className={cn("flex min-w-0 flex-col gap-2", className)}
{...props}
/>
)
}
function FieldLabel({ className, ...props }: React.ComponentProps<"label">) {
return (
<label
data-slot="field-label"
className={cn(
"text-caption font-medium text-muted-foreground",
className
)}
{...props}
/>
)
}
function FieldHint({
className,
error = false,
...props
}: React.ComponentProps<"p"> & {
/** Destructive color plus the input's aria-invalid; the text names the fix. */
error?: boolean
}) {
return (
<p
data-slot="field-hint"
data-error={error || undefined}
role={error ? "alert" : undefined}
className={cn(
"text-caption text-pretty",
error ? "text-destructive" : "text-muted-foreground",
className
)}
{...props}
/>
)
}
export { Field, FieldHint, FieldLabel }