Skip to content
ssntl/ui

Field

A visible, clickable label above the control and a hint below that names the fix when there is an error.

Installation

$ pnpm dlx shadcn@latest add @ssntl/field

Usage

tsx
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

components/ui/field.tsx
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 }