Shake animation
A horizontal shake built with @keyframes — great for signalling an invalid input or a wrong action.
Distance6px
Duration500ms
<button class="shake">Hover to shake</button> :root {
--dist: 6px;
--speed: 500ms;
}
.shake {
padding: 10px 18px;
border: 1px solid #e5484d;
border-radius: 8px;
background: #1b1f2a;
color: #fff;
cursor: pointer;
}
/* Trigger on hover for the demo; in an app, add a .is-invalid class instead */
.shake:hover {
animation: shake var(--speed) ease;
}
@keyframes shake {
10%, 90% { transform: translateX(calc(var(--dist) * -1)); }
20%, 80% { transform: translateX(var(--dist)); }
30%, 50%, 70% { transform: translateX(calc(var(--dist) * -1)); }
40%, 60% { transform: translateX(var(--dist)); }
}
@media (prefers-reduced-motion: reduce) {
.shake:hover { animation: none; }
} How it works
@keyframes defines the steps of a named animation; the animation property
plays it. Here the element jumps left and right at decreasing intervals to read
as a “shake”.
- The keyframe percentages cluster the movement so it’s a quick burst, settling
back to centre at
0%/100%. - It animates
transform: translateX, which is GPU-friendly and won’t shift surrounding layout. - In a real form, toggle a class like
.is-invalid(in JS, on a failed submit) instead of:hover, then remove it after the animation ends. - The
prefers-reduced-motionblock respects users who opt out of motion.
For a looping example instead of a one-shot, see the transitions and animations guide.