Saltar al contenido
VEXA
Formularios

Field

Apto para servidor

Layout wrapper that groups a label, control, description, and error text.

Vista previa en vivo

Renderizado desde el código fuente instalado de VEXA — los mismos archivos que recibes.

Escritorio

Account notifications only.

Lowercase letters and dashes only.

Instalación

Añade el código del componente a tu proyecto. Las dependencias del registry se resuelven automáticamente.

npx shadcn@latest add https://vexa.valfiguer.com/r/field.json

Se instala en components/vexa/field.tsx

Dependencias npm

Sin dependencias npm externas.

Dependencias del registry

Otros items de VEXA que necesita este componente. Instálalos primero — la CLI los resuelve automáticamente.

Código

Cópialo a su ruta destino, o instálalo con el comando de arriba.

components/vexa/field.tsx
import type * as React from "react";

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

/**
 * Field — a vertical layout wrapper that groups a `Label`, a control, and its
 * description / error text with consistent spacing.
 *
 * RSC-safe by design: it uses no React context or hooks, so the accessible
 * wiring stays explicit and server-renderable. Associate parts by id:
 *
 * ```tsx
 * <Field>
 *   <Label htmlFor="email">Email</Label>
 *   <Input id="email" aria-describedby="email-hint" aria-invalid={hasError} />
 *   <FieldDescription id="email-hint">Work address preferred.</FieldDescription>
 *   {hasError ? <FieldError id="email-error">Enter a valid email.</FieldError> : null}
 * </Field>
 * ```
 */
export function Field({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="field"
      className={cn("flex flex-col gap-2", className)}
      {...props}
    />
  );
}

/**
 * FieldDescription — supplementary help text. Reference its `id` from the
 * control's `aria-describedby`.
 */
export function FieldDescription({
  className,
  ...props
}: React.ComponentProps<"p">) {
  return (
    <p
      data-slot="field-description"
      className={cn("text-sm text-vexa-muted-foreground", className)}
      {...props}
    />
  );
}

/**
 * FieldError — an error message. Reference its `id` from the control's
 * `aria-describedby` (or `aria-errormessage`) and set `aria-invalid` on the
 * control. Errors should name both the problem and the recovery action.
 * A leading icon or `"Error:"` prefix keeps meaning from relying on color.
 */
export function FieldError({
  className,
  children,
  ...props
}: React.ComponentProps<"p">) {
  if (children == null || children === false) return null;
  return (
    <p
      data-slot="field-error"
      className={cn(
        "flex items-center gap-1.5 text-sm font-medium text-vexa-danger",
        "[&_svg]:size-4 [&_svg]:shrink-0",
        className,
      )}
      {...props}
    >
      {children}
    </p>
  );
}
JSON del registryr/field.json
r/field.json
{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "field",
  "type": "registry:ui",
  "title": "Field",
  "description": "Layout wrapper that groups a label, control, description, and error text.",
  "registryDependencies": [
    "@vexa/utils",
    "@vexa/label"
  ],
  "files": [
    {
      "path": "registry/default/ui/field.tsx",
      "type": "registry:ui",
      "target": "components/vexa/field.tsx",
      "content": "import type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Field — a vertical layout wrapper that groups a `Label`, a control, and its\n * description / error text with consistent spacing.\n *\n * RSC-safe by design: it uses no React context or hooks, so the accessible\n * wiring stays explicit and server-renderable. Associate parts by id:\n *\n * ```tsx\n * <Field>\n *   <Label htmlFor=\"email\">Email</Label>\n *   <Input id=\"email\" aria-describedby=\"email-hint\" aria-invalid={hasError} />\n *   <FieldDescription id=\"email-hint\">Work address preferred.</FieldDescription>\n *   {hasError ? <FieldError id=\"email-error\">Enter a valid email.</FieldError> : null}\n * </Field>\n * ```\n */\nexport function Field({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"field\"\n      className={cn(\"flex flex-col gap-2\", className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * FieldDescription — supplementary help text. Reference its `id` from the\n * control's `aria-describedby`.\n */\nexport function FieldDescription({\n  className,\n  ...props\n}: React.ComponentProps<\"p\">) {\n  return (\n    <p\n      data-slot=\"field-description\"\n      className={cn(\"text-sm text-vexa-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * FieldError — an error message. Reference its `id` from the control's\n * `aria-describedby` (or `aria-errormessage`) and set `aria-invalid` on the\n * control. Errors should name both the problem and the recovery action.\n * A leading icon or `\"Error:\"` prefix keeps meaning from relying on color.\n */\nexport function FieldError({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"p\">) {\n  if (children == null || children === false) return null;\n  return (\n    <p\n      data-slot=\"field-error\"\n      className={cn(\n        \"flex items-center gap-1.5 text-sm font-medium text-vexa-danger\",\n        \"[&_svg]:size-4 [&_svg]:shrink-0\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </p>\n  );\n}\n"
    }
  ],
  "meta": {
    "frameworks": [
      "react",
      "next"
    ],
    "rsc": "server-safe",
    "exports": [
      "Field",
      "FieldDescription",
      "FieldError"
    ],
    "cssVars": [
      "--vexa-muted-foreground",
      "--vexa-danger"
    ],
    "a11y": "No context/hooks — accessible wiring stays explicit (id + aria-describedby). Errors name the problem and recovery; not color-only.",
    "example": "<Field><Label htmlFor=\"n\">Name</Label><Input id=\"n\" /></Field>",
    "tests": "planned — see docs/README.md#roadmap"
  }
}

Detalles

Accesibilidad

No context/hooks — accessible wiring stays explicit (id + aria-describedby). Errors name the problem and recovery; not color-only.