Saltar al contenido
ssntl/ui

Field

Una etiqueta visible y clicable sobre el control, y una pista debajo que dice cómo corregir un error.

Instalación

$ pnpm dlx shadcn@latest add @ssntl/field

Uso

tsx
import { Field, FieldHint, FieldLabel } from "@/components/ui/field"

<Field>
  <FieldLabel htmlFor="email">Correo de trabajo</FieldLabel>
  <Input id="email" type="email" aria-invalid aria-describedby="email-hint" />
  <FieldHint id="email-hint" error>Agrega el dominio después de la @.</FieldHint>
</Field>
  • Etiquetas clicables: FieldLabel es un <label> real; htmlFor lo une al control.
  • La etiqueta queda visible sobre el control; un placeholder nunca la reemplaza.
  • Un error pone aria-invalid en el control, apunta a la pista con aria-describedby y dice cómo corregirlo.
  • La pista va en tamaño caption y texto muted; destructive solo para errores.

Código

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 }