Shake
FreeNudges a field or control sideways once to say “not accepted”, with an outline instead under reduced motion.
pnpm dlx shadcn@latest add https://caliperui.site/r/shake.jsonWhen to use #
Use it for
- A wrong code, password or answer.
- A refused drop or action.
Skip it when
- Explaining the problem; always write the reason too.
Installation #
terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/shake.json
Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
example.tsx
import { useState } from "react" import { Button } from "@/components/ui/button" import { Field } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { Shake } from "@/components/ui/shake" export function ShakeDemo() { const [code, setCode] = useState(""); const [misses, setMisses] = useState(0); const [error, setError] = useState<string>(); return ( <form className="grid w-full max-w-xs gap-3" onSubmit={(event) => { event.preventDefault(); if (code.trim() === "SAVE20") { setError(undefined); return; } setMisses((n) => n + 1); setError(code.trim() ? `“${code.trim()}” is not a valid code. Try SAVE20.` : "Enter a code."); }} > <Shake signal={misses}> <Field label="Discount code" error={error} description={!error && code.trim() === "SAVE20" ? "Applied: 20% off" : undefined}> {(props) => <Input {...props} value={code} onChange={(event) => setCode(event.target.value)} autoComplete="off" />} </Field> </Shake> <Button type="submit" size="sm" className="w-fit">Apply</Button> </form> ); }
API reference #
Shake
component
Also accepts the native props of its root element and a ref.
PropTypeDefaultDescription
signal *number—Bump it to shake again, e.g. the number of failed attempts.
Accessibility #
It is built on native elements, so roles and focus come from the browser.
No custom key handling; Tab and the native activation keys do the work.
Motion #
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.