Skip to content
VEXA
Forms

Field

Server-safe

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

Live preview

Rendered from the installed VEXA source — the same files you receive.

Account notifications only.

Lowercase letters and dashes only.

Installation

Adds the component source to your project. Registry dependencies are pulled in automatically.

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

Installs to components/vexa/field.tsx

npm dependencies

No external npm dependencies.

Registry dependencies

Other VEXA items this component needs. Install them first — they are resolved automatically by the CLI.

Source

Copy this into its target path, or install it with the command above.

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

Details

Example

tsx
<Field><Label htmlFor="n">Name</Label><Input id="n" /></Field>

Accessibility

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