Input
A native text input drawn like the Select trigger, with icons, addons, a floating label, clear and password buttons, and a one-time code. Every animation is CSS.
We only use it to sign you in.
Installation
Add the registry to components.json, once
{
"registries": {
"@rabtx": "http://localhost:3002/r/{name}.json"
}
}Add the component
npx shadcn@latest add @rabtx/inputOr, without the namespace, by URL:
npx shadcn@latest add http://localhost:3002/r/input.jsonThe files land in your components/ui and are styled by your own theme. Add --overwrite to replace a shadcn component with the same name.
Usage
Installed from the registry, import from @/components/ui/input; inside this monorepo, from
@rabtx/ui/input.
import { Field } from "@rabtx/ui/field";
import { Input } from "@rabtx/ui/input";
export function EmailField() {
return (
<Field label="Email" hint="We only use it to sign you in.">
<Input type="email" placeholder="you@example.com" />
</Field>
);
}It is a real <input> inside a wrapper that draws the field, so type, autocomplete, native
validation and phone keyboards all work. Inside a Field it receives its id, its description and
the invalid state. Outside one, give it a floating label or an aria-label.
With a Select and a Button
The default look is the Select trigger's: the same fill, edge, corners, hover, focus ring and error, so an Input, a Select and a Button of one size line up in a row.
Variants
outline (the default) matches the Select. filled is the recessed field from the design
system: a soft fill with a dark top edge fading to a light bottom edge. In data-depth="flat" it
becomes one plain border.
Icons and shape
leadingIcon and trailingIcon are decorative (hidden from screen readers) and sized for you.
pill makes the field fully round, for search bars, independent of the radius mode. Clicking an
icon or the padding focuses the input.
Addons
startAddon and endAddon are segments at either end, divided from the text by a line. Their
text is part of the page, so screen readers read it.
Search and clear
clearable adds a clear button that is there only while the input has text. It empties the input
the way typing would, so a controlled input gets its onChange, then returns focus. onClear
runs afterwards; clearLabel names the button (default "Clear").
Query: Karachi
Password
PasswordInput adds an eye button. Its name stays "Show password" and its aria-pressed says
whether the password is showing; the two eyes cross-fade with a little blur.
At least 12 characters.
Floating label
label puts the label inside the field. It rests where the text goes and floats to the top edge,
at 80%, on focus or once the input has a value (including autofill). A placeholder then shows
only while focused. Use it at md or lg; sm grows to md. Use it instead of a Field
label, not with one.
States
- Error: a
Fielderror(oraria-invalid) turns the edge red and shakes the field once. Native constraints (required,type="email",pattern,minLength) do the same through:user-invalid, only after the person has edited the field and left it. - Success:
successdraws a green tick at the end. Say why in the hint, since the tick is decorative. - Disabled fades to 50%. Read-only tints the fill and hides the clear button.
That name is taken.
Checked by the browser once you leave the field.
Available.
One-time code
InputOTP is one real input shown as a row of slots, so typing, backspace, paste and SMS autofill
(autocomplete="one-time-code") work as usual. A single focus ring glides to the next slot, a
caret blinks there, and each character rolls up into its slot. onComplete fires when the last
slot fills. It sets a pattern, so an incomplete code is :user-invalid after the person leaves
it.
Sent to •••• 4821.
That PIN is not right.
API
Input takes everything a native <input> takes, except the numeric size, plus:
Prop
Type
PasswordInput takes the same props (no type or action) plus revealLabel
(default "Show password").
InputOTP takes the native input props plus:
Prop
Type
Outside React, the class names are exported from @rabtx/ui/input/styles:
<div class="rx-input-field rx-input-md">
<span class="rx-input-icon" aria-hidden="true"><svg>…</svg></span>
<input class="rx-input" aria-label="Search" placeholder="Search" />
</div>Give the input a placeholder (a single space will do): :placeholder-shown is how the CSS knows
it is empty, for the clear button and the floating label.
Theming
The Input reads only the theme tokens.
| Part | Tokens |
|---|---|
| Field | fill --card, text --card-foreground, edge --input, corners --radius-box |
| Filled variant | --field, --field-edge-top, --field-edge-bottom, --field-shade, --field-disabled |
| Icons and addons | --muted-foreground, addon fill --secondary |
| Placeholder and resting label | --subtle-foreground |
| Focus, error, success | --ring, --destructive, --success |
| Buttons inside | hover --accent / --accent-foreground, corners --radius-item |
| Font and motion | --font-sans, --spring-label, --spring-swap, --motion-ease |
How the motion works
| Effect | CSS |
|---|---|
| Focus and error | the wrapper reads the input with :has(.rx-input:focus-visible) and :has(.rx-input:is([aria-invalid="true"], :user-invalid)), and transitions its edge and a 3px color-mix() halo |
| Shake | the error rule also sets a 400ms translate keyframe, so it plays each time the field turns invalid, with no JavaScript |
| Floating label | .rx-input:is(:focus, :not(:placeholder-shown), :autofill) + label moves and scales it on the label spring (a linear() easing) |
| Clear button | hidden by :has(.rx-input:placeholder-shown); it pops in on the swap spring |
| Password eye | both glyphs are always there; aria-pressed cross-fades them with scale and a 4px blur, shorter on the way out |
| Success tick | the path has pathLength="1" and a stroke-dashoffset keyframe draws it |
| Code slots | the ring's translate follows --_at (the next slot, set by React) on the swap spring; each new character mounts with a roll-up keyframe; the caret is a step-end blink |
Reduced motion and data-motion="off" drop the shake, float, glide and roll; every state still
shows.
Browser support
:has(),:user-invalid,:autofillandlinear()easings: current Chrome, Edge, Safari and Firefox. Without:user-invalidnative errors simply do not turn red;Fielderrors still do.
Accessibility
- A real input: its role, value, validation and autocomplete come from the browser.
- Decorative icons are hidden; addons, the clear button ("Clear") and the eye ("Show password", pressed or not) are named.
- The code input is a single text field with
autocomplete="one-time-code", so screen readers read and edit one value; the slots are hidden from them. - Focus shows a blue edge and a soft ring. Error shows a red edge and ring.
Collapsible
A native details element that opens to its own height, fades its content in out of a blur and turns its chevron over. Controlled or not, and opened by find in page. Every animation is CSS.
Card
A surface in shadcn's parts, plus link, selectable, tilt, spotlight, expandable and stacked cards. Every animation is CSS.