8.2 KiB
Input
(no submodules)
- input-number — (no submodules)- input — (no submodules)- input-number — _(no submodu…
- input.variants —
inputVariantsis the cva (class-variance-authority) configuration that powers every<Input>render. It is split fro… - input-number — (no submodules)- input — (no submodules)- input.variants —
inputVariantsis 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 —
inputVariantsis 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 —
inputVariantsis the cva (class-variance-authority) configuration that powers every<Input>render. It is split fro… - input-number — (no submodules)- input — (no submodules)- input.variants —
inputVariantsis 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
InputHTMLAttributesare forwarded as-is (onChange,value,defaultValue,name,autoComplete,inputMode,pattern, etc.). placeholdertyped asTranslationKey | undefined— the chassis enforces that the placeholder text is a registered translation key, never a raw English literal. Passingundefinedskips the attribute entirely.invalidboolean prop drivesdata-invalid="true"on the rendered element so the cva config'sdata-[invalid=true]:border-destructiverule activates.disabledmirrors asdata-disabled="true"for the parity with shadcn's selector convention.- Theme variant overrides via
useThemedVariants("Input", inputVariants). AThemePlugin.variantOverrides.Inputmay add brand-new size or appearance variants (see the type-safety trade-off inproposal.md§"Type-safety trade-off"). motion-reduce:transition-nonecollapses focus/border transitions for users withprefers-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-mediumso<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 thereact-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.placeholderis omitted entirely whenundefined, 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-invalidanddata-disabledare written whentrue, omitted entirely whenfalseorundefined. The CVA selector convention isdata-[invalid=true]:…so an explicitdata-invalid="false"would not match, but it would clutter the rendered HTML.- The chassis does NOT inject any
aria-invalidattribute. Consumers who wantaria-invalid="true"MUST pass it themselves alongsideinvalid. 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.