Files
skills/docs/ui/react/react-ui/src/components/field/field.variants.md
2026-05-29 08:20:56 +01:00

3.2 KiB

Field Variants (cva config)

Purpose

fieldVariants, fieldLabelVariants, fieldDescriptionVariants, and fieldErrorVariants are the four cva (class-variance-authority) configurations powering the <Field> primitives. They are split from field.tsx into their own module so an ejected source copy under npx shadcn@latest add @suaveplan/field keeps the cva configurations in a dedicated file. Theme overrides flow through useThemedVariants("Field" | "FieldLabel" | "FieldDescription" | "FieldError").

The container cva uses Tailwind's named-group selector (group/field) so the descendant <FieldLabel> instances can react to data-invalid="true" on the container without depending on direct-parent semantics.

Features

  • Container flex flex-col gap-2 layout (NOT space-y-* per shadcn skill rule).
  • data-[invalid=true]:[&_label]:text-destructive on the container — every descendant <label> (including nested <FieldLabel>) flips colour.
  • data-[disabled=true]:opacity-70 decay.
  • <FieldLabel> cva inherits the destructive colour via group-data-[invalid=true]/field:text-destructive.
  • <FieldDescription> muted via text-sm text-muted-foreground.
  • <FieldError> destructive via text-sm font-medium text-destructive.

Usage: Basic Example

import {
    fieldVariants,
    fieldLabelVariants,
    fieldErrorVariants,
} from "@suaveplan/react-ui";

const containerCls = fieldVariants();
const labelCls = fieldLabelVariants();
const errorCls = fieldErrorVariants();

Usage: Advanced Example

import { fieldVariants, cn } from "@suaveplan/react-ui";

export function CustomField({
    invalid,
    children,
}: {
    invalid: boolean;
    children: React.ReactNode;
}) {
    return (
        <div
            data-invalid={invalid ? "true" : undefined}
            className={cn(fieldVariants(), "rounded border p-2")}
        >
            {children}
        </div>
    );
}

API Reference

fieldVariants(): string

Returns the container classes — group/field flex flex-col gap-2 ….

fieldLabelVariants(): string

Returns the label classes including the group-data-[invalid=true]/field:text-destructive selector.

fieldDescriptionVariants(): string

Returns the muted helper-text classes.

fieldErrorVariants(): string

Returns the destructive error-text classes.

Implementation Notes

  • The named group group/field is named (not the bare group class) so nested <Field> instances do not collide — Tailwind treats group/field as a distinct selector from a parent group/section.
  • The container cva applies the destructive colour to ALL descendant <label> elements ([&_label]:text-destructive) so even a hand-rolled <label> inside <Field> picks up the colour without needing <FieldLabel>. This is intentional: consumers using the form library may render their own labels via react-hook-form field arrays.
  • Per the shadcn skill rule, the cva NEVER uses space-y-* / space-x-* — gap-* flows the same in RTL.
  • <FieldError> inherits the destructive colour directly (via text-destructive) rather than via the group selector so it always reads as an error even when <Field invalid> is not set (e.g. server-side error surfaces).