Input with motion: a short shake when it turns invalid, and its error message grows in under the box and folds away when it's fixed.
$ pnpm dlx shadcn@latest add https://dashral.com/r/motion-input.jsonInstallation
$ pnpm dlx shadcn@latest add https://dashral.com/r/motion-input.jsonAdds components/dashral/motion/input.tsx, the Input it builds on (in components/dashral/), motion and the motion files every motion component shares. With the registry in your components.json:
$ pnpm dlx shadcn@latest add @dashral/motion-inputUsage
The same as Input, imported from components/dashral/motion/: swap the import and it moves.
import { Input } from "@/components/dashral/motion/input"
<Input label="Email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} error={errors.email} />Examples
A sign-up form
Press Create with a bad email or a short password: that field shakes and its error grows in, and shakes again at each press until it's fixed. Fix it, and Creating… gives way to a welcome.
Compared with Default
The error message fades in under the box; the lines below jump down to make room.
Motion
The box gives a short shake as it turns invalid; the error grows in, the lines below gliding down, folds away when fixed, and a new one crosses over the old.
How the two editions differ in general, and which to use where: Motion.
API reference
Every prop of Input, and every attribute of the element it renders. The Motion edition adds no props of its own.
Input
Also takes every prop of <input>(opens in a new tab).
| Prop | Type | Default |
|---|---|---|
label | ReactNodeShown above, linked to the control. | — |
description | ReactNodeA hint under the control. | — |
error | boolean | ReactNodetrue shows the error style; a message also shows under the control. | — |
labelAction | ReactNodeAt the end of the label's row, e.g. a “Forgot password?” link or the current value. | — |
withAsterisk | booleanMarks the label with an asterisk, e.g. alongside required. | false |
prefix / suffix | ReactNodeInside the box, before or after the text: an icon, text, or a button. | — |
unstyled | booleanDrops the border, background and focus ring. | false |
revealable | booleanWith type="password": a button at the end that shows and hides the password, after any suffix. false turns it off. | true |
format | "card" | "phone" | "date" | "expiry" | "time" | "number" | { type: "number", decimals } | { pattern }Formats as you type and keeps the caret in place: card groups and brand, the locale's date order, thousands separators, or a pattern (# digit, A letter). Sets the keyboard and autofill hints. No library. | — |
onValueChange | (raw, { formatted, brand }) => voidWith format: the raw value (digits, or "1234.5" for numbers), the text as shown, and a card's brand. | — |
classNames | { root, label, wrapper, input, prefix, suffix, description, error }Classes for each part. | — |
className | stringExtra classes, merged with cn() so yours win. | — |
Motion
- When it turns invalid, or the error changes, the box shakes sideways a few pixels and settles (360ms), affixes and all; and again at every try to submit its form while it's still invalid, or when the browser's own check (required, type="email") finds it invalid.
- The error message grows in under the box (--duration-standard), so the hint and anything below glide down instead of jumping; it folds away (--duration-fast) when the error clears.
- A new message fades in rising 4px as the old one fades out, in the same space.
- With reduced motion nothing shakes, and messages only appear and disappear.