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

8.2 KiB

Input

(no submodules)

  • input-number — (no submodules)- input — (no submodules)- input-number — _(no submodu…
  • input.variants — inputVariants is the cva (class-variance-authority) configuration that powers every <Input> render. It is split fro…
  • input-number — (no submodules)- input — (no submodules)- input.variants — inputVariants is the cva (class-variance-authority) configuration that powers every <Input> render. It is split fro…
  • input-number — (no submodules)- input — (no submodules)- input-number — <!-- begin:au…
  • input.variants — inputVariants is the cva (class-variance-authority) configuration that powers every <Input> render. It is split fro…
  • input-number — (no submodules)- input — (no submodules)- input-number — _(no submod…
  • input.variants — inputVariants is the cva (class-variance-authority) configuration that powers every <Input> render. It is split fro…
  • input-number — (no submodules)- input — (no submodules)- input.variants — inputVariants is the cva (class-variance-authority) configuration that powers every <Input> render. It is split fro…

Purpose

<Input> is the chassis primitive form input for @suaveplan/react-ui. It renders a native <input> element with theme-driven variant resolution, forwards refs to the underlying DOM node, and threads its placeholder through the active i18n instance so consumers can never accidentally ship a hardcoded English placeholder. The component is intentionally thin — the shadcn upstream Input recipe is structurally simple and the chassis follows suit, exposing a single default variant + default size at the cva level so themes carry the visual variation rather than forcing the component to enumerate it.

<Input> participates in the same data-* attribute convention as the rest of @suaveplan/react-ui: data-invalid="true" and data-disabled="true" are written when the corresponding props are set, so theme overrides can target error state via a single CSS selector without depending on aria-invalid (which the consumer may or may not have set independently).

Features

  • Forwards refs to the underlying <input> so consumers can focus, select, measure, or animate it directly.
  • All native InputHTMLAttributes are forwarded as-is (onChange, value, defaultValue, name, autoComplete, inputMode, pattern, etc.).
  • placeholder typed as TranslationKey | undefined — the chassis enforces that the placeholder text is a registered translation key, never a raw English literal. Passing undefined skips the attribute entirely.
  • invalid boolean prop drives data-invalid="true" on the rendered element so the cva config's data-[invalid=true]:border-destructive rule activates.
  • disabled mirrors as data-disabled="true" for the parity with shadcn's selector convention.
  • Theme variant overrides via useThemedVariants("Input", inputVariants). A ThemePlugin.variantOverrides.Input may add brand-new size or appearance variants (see the type-safety trade-off in proposal.md §"Type-safety trade-off").
  • motion-reduce:transition-none collapses focus/border transitions for users with prefers-reduced-motion: reduce.
  • Focus styling via focus-visible:ring-2 focus-visible:ring-ring — keyboard users get a visible ring; pointer focus does not.
  • File-input styling via file:border-0 file:bg-transparent file:text-sm file:font-medium so <Input type="file"> inherits a sensible button look.

Usage: Basic Example

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

export function NameField({
    onChange,
}: {
    onChange: (next: string) => void;
}) {
    return (
        <Input
            name="full-name"
            autoComplete="name"
            onChange={(event) => onChange(event.currentTarget.value)}
        />
    );
}

Usage: Advanced Example

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

export function EmailField({
    value,
    error,
    onChange,
}: {
    value: string;
    error: string | undefined;
    onChange: (next: string) => void;
}) {
    return (
        <Input
            type="email"
            name="email"
            autoComplete="email"
            inputMode="email"
            placeholder="react-ui.empty.noResults"
            value={value}
            invalid={error !== undefined}
            aria-invalid={error !== undefined}
            aria-describedby={error !== undefined ? "email-error" : undefined}
            onChange={(event) => onChange(event.currentTarget.value)}
        />
    );
}

API Reference

<Input>

forwardRef<HTMLInputElement, InputProps> — renders a <input type={type}> (default "text").

InputProps

Prop Type Default Description
variant "default" "default" Visual variant. The chassis ships only default; themes may extend.
size "default" "default" Sizing variant. The chassis ships only default; themes may extend.
placeholder TranslationKey | undefined undefined Translation key resolved against the active i18n instance. Hardcoded literals are rejected at compile time.
invalid boolean undefined When true, writes data-invalid="true" on the element so the cva error rules activate.
disabled boolean undefined Forwarded to the native <input> AND mirrored as data-disabled="true".
type native "text" Native input type.
all native InputHTMLAttributes<HTMLInputElement> native native Forwarded as-is.

inputVariants

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

Errors

  • useTranslation() is called unconditionally inside the component. When the surrounding tree lacks a <TranslationProvider> (or <ReactUiProvider>), the hook returns its identity-fallback shape — t(key) returns the key as the literal. This matches the react-i18n "no-throw in production" contract; the component renders, but the rendered placeholder is the raw key.

Implementation Notes

  • The variants module is intentionally separate (input.variants.ts) so an ejected source copy keeps the cva config in a dedicated file, mirroring the upstream shadcn convention.
  • useThemedVariants("Input", inputVariants) is called unconditionally so the hook order is stable across renders. The hook's memoisation key is (themeName, "Input") so a re-render with the same theme returns the same resolver reference.
  • placeholder is omitted entirely when undefined, NOT set to the empty string. An empty placeholder attribute would still allocate the placeholder pseudo-element in the user-agent stylesheet and could collide with screen-reader heuristics.
  • data-invalid and data-disabled are written when true, omitted entirely when false or undefined. The CVA selector convention is data-[invalid=true]:… so an explicit data-invalid="false" would not match, but it would clutter the rendered HTML.
  • The chassis does NOT inject any aria-invalid attribute. Consumers who want aria-invalid="true" MUST pass it themselves alongside invalid. This separation lets the component represent purely visual error state (e.g. "the user has not yet attempted to submit, but we want to indicate the field is required").
  • <Input type="file"> inherits the file-input styling rules from the cva base; consumers do not need to layer additional classes for the upload button.