shake-wiggleShake / Wiggle
Use one brief horizontal shake for errors or inputs that need attention.
Preview
A rapid alternating displacement flags invalid input or a rejected action.
FullReduced
FullReduced
Preview
Implement Shake / Wiggle: Use one brief horizontal shake for errors or inputs that need attention. Use Duration 240ms, Distance 10px, Cycles 3. Trigger it once when state changes and keep it replayable. Use semantic HTML, scope CSS to .motion-demo--shake-wiggle. Provide equivalent low-motion behavior for prefers-reduced-motion and .force-reduced-motion.
<div class="motion-demo motion-demo--shake-wiggle" data-motion="shake-wiggle" aria-label="Shake / Wiggle demo">
<article class="motion-surface">
<strong>Shake / Wiggle</strong>
<span class="motion-line"></span>
<span class="motion-line"></span>
</article>
</div>.motion-demo.motion-demo--shake-wiggle {
box-sizing: border-box;
position: relative;
display: grid;
place-items: center;
width: min(100%, 34rem);
min-height: 15rem;
padding: 2rem;
overflow: hidden;
color: #18181b;
background: #fafafa;
border: 1px solid #e4e4e7;
border-radius: 16px;
isolation: isolate;
}
.motion-demo.motion-demo--shake-wiggle *, .motion-demo.motion-demo--shake-wiggle *::before, .motion-demo.motion-demo--shake-wiggle *::after {
box-sizing: border-box;
}
.motion-demo.motion-demo--shake-wiggle .motion-surface {
position: relative;
display: grid;
gap: 0.65rem;
width: min(100%, 18rem);
padding: 1.25rem;
background: #ffffff;
border: 1px solid #e4e4e7;
border-radius: 16px;
box-shadow: none;
}
.motion-demo.motion-demo--shake-wiggle .motion-surface strong { font-weight: 500; }
.motion-demo.motion-demo--shake-wiggle .motion-line {
display: block;
width: 100%;
height: 0.5rem;
background: #e4e4e7;
border-radius: 999px;
}
.motion-demo.motion-demo--shake-wiggle .motion-line:last-child { width: 62%; }
.motion-demo.motion-demo--shake-wiggle .motion-button {
position: relative;
min-height: 2.75rem;
padding: 0.75rem 1.1rem;
color: white;
font: inherit;
font-weight: 700;
background: #18181b;
border: 0;
border-radius: 999px;
cursor: pointer;
}
.motion-demo.motion-demo--shake-wiggle .motion-button:focus-visible { outline: 3px solid rgb(37 99 235 / 32%); outline-offset: 3px; }
.motion-demo.motion-demo--shake-wiggle .motion-action-row {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-top: 0.85rem;
}
.motion-demo.motion-demo--shake-wiggle .motion-replay-scene {
width: min(100%, 22rem);
display: grid;
place-items: center;
}
.motion-demo.motion-demo--shake-wiggle .motion-pausable-scene {
width: min(100%, 22rem);
display: grid;
place-items: center;
}
.motion-demo.motion-demo--shake-wiggle button.motion-surface {
color: inherit;
font: inherit;
text-align: start;
cursor: pointer;
}
.motion-demo.motion-demo--shake-wiggle .motion-replay-button,
.motion-demo.motion-demo--shake-wiggle .motion-undo-button,
.motion-demo.motion-demo--shake-wiggle .motion-dismiss-button,
.motion-demo.motion-demo--shake-wiggle .motion-pause-button {
min-height: 2.75rem;
padding: 0.65rem 0.9rem;
color: #18181b;
font: 650 0.78rem/1 ui-sans-serif, system-ui, sans-serif;
background: #ffffff;
border: 1px solid #d4d4d8;
border-radius: 8px;
cursor: pointer;
}
.motion-demo.motion-demo--shake-wiggle .motion-replay-button:focus-visible,
.motion-demo.motion-demo--shake-wiggle .motion-undo-button:focus-visible,
.motion-demo.motion-demo--shake-wiggle .motion-dismiss-button:focus-visible,
.motion-demo.motion-demo--shake-wiggle .motion-pause-button:focus-visible,
.motion-demo.motion-demo--shake-wiggle [data-reorder-item]:focus-visible,
.motion-demo.motion-demo--shake-wiggle [data-swipe-target]:focus-visible {
outline: 3px solid rgb(37 99 235 / 32%);
outline-offset: 3px;
}
.motion-demo.motion-demo--shake-wiggle .motion-status {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.motion-demo.motion-demo--shake-wiggle .motion-ripple-ink {
position: absolute;
width: 12px;
height: 12px;
pointer-events: none;
background: rgb(255 255 255 / 72%);
border-radius: 50%;
transform: translate(-50%, -50%) scale(0.95);
opacity: 0;
}
.motion-demo.motion-demo--shake-wiggle .motion-state-stack { position: relative; width: min(100%, 18rem); height: 9rem; }
.motion-demo.motion-demo--shake-wiggle .motion-state { position: absolute; inset: 0; display: grid; place-items: center; padding: 1rem; background: #ffffff; border: 1px solid #e4e4e7; border-radius: 16px; }
.motion-demo.motion-demo--shake-wiggle .motion-state--to { background: #18181b; color: white; }
.motion-demo.motion-demo--shake-wiggle .motion-scroll-track > span { color: #71717a; font: 600 0.72rem/1.2 ui-monospace, monospace; letter-spacing: 0.08em; text-transform: uppercase; }
.motion-demo.motion-demo--shake-wiggle .motion-orbit-item { display: grid; place-items: center; width: 2.5rem; height: 2.5rem; color: white; background: #2563eb; border-radius: 50%; }
.motion-demo.motion-demo--shake-wiggle .motion-text-stack { position: relative; width: min(100%, 18rem); min-height: 4rem; }
.motion-demo.motion-demo--shake-wiggle svg { width: min(100%, 20rem); height: auto; }
.motion-demo.motion-demo--shake-wiggle .motion-surface { animation: motion-shake-wiggle 240ms cubic-bezier(0.77, 0, 0.175, 1) both; border-color: #b34d37; }
@keyframes motion-shake-wiggle { 0%, 100% { transform: none; } 14% { transform: translateX(10px); } 29% { transform: translateX(-9px); } 43% { transform: translateX(7px); } 57% { transform: translateX(-6px); } 71% { transform: translateX(4px); } 86% { transform: translateX(-3px); } }
@keyframes motion-shake-wiggle-reduced-fade {
from { opacity: 0.84; }
to { opacity: 1; }
}
.force-reduced-motion .motion-demo.motion-demo--shake-wiggle {
animation: motion-shake-wiggle-reduced-fade 160ms cubic-bezier(0.23, 1, 0.32, 1) both !important;
}
.force-reduced-motion .motion-demo.motion-demo--shake-wiggle *,
.force-reduced-motion .motion-demo.motion-demo--shake-wiggle *::before,
.force-reduced-motion .motion-demo.motion-demo--shake-wiggle *::after {
animation: none !important;
animation-delay: 0ms !important;
animation-iteration-count: 1 !important;
transition-property: opacity, color, background-color, border-color !important;
transition-duration: 160ms !important;
transition-delay: 0ms !important;
scroll-behavior: auto !important;
}
.force-reduced-motion .motion-demo.motion-demo--shake-wiggle .motion-button,
.force-reduced-motion .motion-demo.motion-demo--shake-wiggle [data-reorder-item],
.force-reduced-motion .motion-demo.motion-demo--shake-wiggle [data-swipe-target],
.force-reduced-motion .motion-demo.motion-demo--shake-wiggle [data-spring-target] {
transform: none !important;
}
.force-reduced-motion .motion-demo.motion-demo--shake-wiggle .motion-path { stroke-dashoffset: 0 !important; }
.force-reduced-motion .motion-demo.motion-demo--shake-wiggle .motion-word--from { opacity: 0 !important; visibility: hidden; }
.force-reduced-motion .motion-demo.motion-demo--shake-wiggle .motion-word:not(.motion-word--from) { opacity: 1 !important; filter: none !important; transform: none !important; }
@media (prefers-reduced-motion: reduce) {
.motion-demo.motion-demo--shake-wiggle {
animation: motion-shake-wiggle-reduced-fade 160ms cubic-bezier(0.23, 1, 0.32, 1) both !important;
}
.motion-demo.motion-demo--shake-wiggle *, .motion-demo.motion-demo--shake-wiggle *::before, .motion-demo.motion-demo--shake-wiggle *::after {
animation: none !important;
animation-delay: 0ms !important;
animation-iteration-count: 1 !important;
transition-property: opacity, color, background-color, border-color !important;
transition-duration: 160ms !important;
transition-delay: 0ms !important;
scroll-behavior: auto !important;
}
.motion-demo.motion-demo--shake-wiggle .motion-button,
.motion-demo.motion-demo--shake-wiggle [data-reorder-item],
.motion-demo.motion-demo--shake-wiggle [data-swipe-target],
.motion-demo.motion-demo--shake-wiggle [data-spring-target] {
transform: none !important;
}
.motion-demo.motion-demo--shake-wiggle .motion-path { stroke-dashoffset: 0 !important; }
.motion-demo.motion-demo--shake-wiggle .motion-word--from { opacity: 0 !important; visibility: hidden; }
.motion-demo.motion-demo--shake-wiggle .motion-word:not(.motion-word--from) { opacity: 1 !important; filter: none !important; transform: none !important; }
}Learn more
Terms and distinctions
shake-wiggleShake / Wiggle
摇晃 / 抖动English definition
A rapid alternating displacement flags invalid input or a rejected action.
Chinese translation
快速左右抖动,用来提示错误或输入被拒绝。
Before implementation
- Purpose
- Use one brief horizontal motion to signal an error, rejection, or input needing attention.
- Frequency
- Use infrequently for explicit errors and avoid repetition during continuous validation.
- Trigger
- Trigger on submission failure or server rejection while showing readable error text.
- Enter / exit
- Run 2–3 decaying oscillations when the error appears, with no reverse animation when cleared.
- Interruptibility
- Stop shaking on new input and retrigger only after another submission.
- Gesture and keyboard
- No gesture is required; move focus to an error summary or first invalid field with a clear keyboard path.
- Reduced-motion strategy
- Remove travel and use error border, icon, and text, with at most one brief color pulse.
Accessibility
Reduced motion
Remove travel and use error border, icon, and text, with at most one brief color pulse.
Parameter reference
| Parameter | Range / options | Default | Purpose |
|---|---|---|---|
duration | 80ms–1200ms | 240ms | Controls the time required for one motion cycle. |
distance | 0px–30px | 10px | Controls how far the element travels. |
cycles | 1–6 | 3 | Adjusts cycles. |
Pre-delivery review
- Never rely on shaking in place of error copy.
- Keep amplitude small and avoid moving a page or large container.