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

6.7 KiB

Label

(no submodules)

- label.variants — `labelVariants` is the cva (class-…
  • label.variants — labelVariants is the cva (class-variance-authority) configuration that powers every <Label> render. It is split fro…

  • label.variants — labelVariants is the cva (class-…

  • label.variants — labelVariants is the cva (class-variance-authority) configuration that powers every <Label> render. It is split fro…

  • label.variants — labelVariants is the cva (class-…

  • label.variants — labelVariants is the cva (class-variance-authority) configuration that powers every <Label> render. It is split fro…

  • label.variants — labelVariants is the cva (class-…

  • label.variants — labelVariants is the cva (class-variance-authority) configuration that powers every <Label> render. It is split fro…

  • label.variants — labelVariants is the cva (class-variance-authority) configuration that powers every <Label> render. It is split fro…

Purpose

<Label> is the chassis form-label primitive for @suaveplan/react-ui. It wraps @radix-ui/react-label so consumers inherit the Radix labelling contract (a click on the label focuses the matched input via htmlFor, and synthetic click events originating inside the label are not double-fired on the input) while threading variant resolution through useThemedVariants("Label") and surfacing a data-disabled="true" mirror so visual disabled state can be represented without needing a sibling form control.

The label children are deliberately NOT translated by the chassis. Labels routinely interleave dynamic inline elements (<strong>, <span>, <Trans>, count badges, asterisks for required fields) and forcing the children through a single string-key lookup would prevent that composition. Consumers compose translation calls in their own JSX, e.g. <Label htmlFor="email">{t('app.email.label')}</Label> or <Label htmlFor="email"><Trans i18nKey="app.email.label" /></Label>.

Features

  • Wraps @radix-ui/react-label for full Radix labelling semantics (focus management on click, no double-fire of synthetic events).
  • Forwards refs to the underlying <label> element.
  • All native LabelHTMLAttributes are forwarded as-is via the Radix primitive's props passthrough.
  • disabled boolean prop writes data-disabled="true" on the rendered label so the cva config's data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-70 rules activate.
  • peer-disabled: Tailwind variant baked into the cva base so a label adjacent to a peer-classed input picks up the disabled styling automatically when the input is disabled.
  • Theme variant overrides via useThemedVariants("Label", labelVariants). A ThemePlugin.variantOverrides.Label may add brand-new variants (e.g. variant: "section-heading").
  • No translation lookup on children — composition is consumer-driven.

Usage: Basic Example

import { Label, Input } from "@suaveplan/react-ui";

export function EmailField() {
    return (
        <div className="flex flex-col gap-1">
            <Label htmlFor="email">Email</Label>
            <Input id="email" type="email" />
        </div>
    );
}

Usage: Advanced Example

import { Label, Input } from "@suaveplan/react-ui";
import { useTranslation } from "@suaveplan/react-i18n";

export function PasswordField({ disabled }: { disabled: boolean }) {
    const { t } = useTranslation();
    return (
        <div className="flex flex-col gap-1">
            <Label htmlFor="password" disabled={disabled}>
                {t("app.password.label")}{" "}
                <span aria-hidden="true" className="text-destructive">
                    *
                </span>
            </Label>
            <Input
                id="password"
                type="password"
                disabled={disabled}
                className="peer"
                autoComplete="current-password"
            />
        </div>
    );
}

API Reference

<Label>

forwardRef<HTMLLabelElement, LabelProps> — renders a Radix <Label.Root> element (which itself renders a native <label> with the labelling enhancements applied).

LabelProps

Prop Type Default Description
htmlFor string none Required. The id of the form control this label is bound to.
variant "default" "default" Visual variant; the chassis ships only default so themes carry the variation.
size "default" "default" Sizing variant; the chassis ships only default so themes carry the variation.
disabled boolean undefined When true, writes data-disabled="true" so the cva opacity decay activates.
all ComponentPropsWithoutRef<typeof LabelPrimitive.Root> native native Forwarded as-is to Radix.

labelVariants

The bare cva resolver. Exported so external consumers (@suaveplan/react-ui-theme-*, ejected source under npx shadcn@latest add @suaveplan/label) can re-use the variants directly without going through useThemedVariants.

Errors

The Label component does NOT call useTranslation() so it does not interact with the i18n provider at all. Consumers that wrap <Trans> or useTranslation themselves inherit the upstream provider's no-throw-in-production contract.

Implementation Notes

  • The chassis re-exports Radix's labelling semantics rather than re-implementing them: the click-on-label-focuses-input + click-event-bubble-suppression behaviour is non-trivial to get right, and Radix is the upstream-of-record.
  • data-disabled="true" is written when the prop is true, omitted when false/undefined. The CVA selector convention is data-[disabled=true]:…, so an explicit data-disabled="false" would not match but it would clutter the DOM.
  • The peer-disabled:cursor-not-allowed peer-disabled:opacity-70 rules in the cva base let consumers wire a label adjacent to a peer-classed input — when the input is disabled, the label picks up the visual decay automatically without requiring the consumer to thread the disabled prop through both elements.
  • The variants module is intentionally separate (label.variants.ts) so an ejected source copy keeps the cva config in a dedicated file, mirroring the upstream shadcn convention.
  • useThemedVariants("Label", labelVariants) is called unconditionally so the hook order is stable across renders. The hook's memoisation key is (themeName, "Label") so a re-render with the same theme returns the same resolver reference.
  • Refs forward to the underlying <label> (Radix Label.Root is implemented as a <label> element, not a custom-element wrapper).