AI CSS Animations

Guides · · Maxwell Prehoda

13 Button Click Animation Examples That Make Every Press Feel Real

A good click animation answers one question within a frame or two: did that register? The press itself should be nearly instant (GitHub's buttons use no transition at all on press, Josh W. Comeau's 3D button uses 34ms), and the personality goes into the release and into what happens next: a ripple, a spinner that becomes a check, a burst, or confetti for the rare big moment.

Below are 13 examples, from design systems and libraries to tutorials and Codrops experiments, with the timings from their source code or docs, what each is best for and the catch. Most have a live pure-CSS demo from our free directory that you can press right here and copy. Hover effects are a different job, covered in our button hover effect examples.

1. Emil Kowalski's 0.97 press: Best for making any primary button feel pressable

Platform: Pure CSS technique, one :active rule

Live from our directory: Pressed Button Effect. Copy the HTML and CSS →

The simplest click animation worth shipping comes from Emil Kowalski's 7 practical animation tips: scale the button to 0.97 on :active. The button shrinks by 3% while the pointer is down, which reads as a physical press without anyone noticing an animation.

The same post warns against the mirror mistake, animating an element in from scale(0); start from 0.9 or higher so it never appears out of nothing. It also suggests keeping UI animations under 300ms. Our Pressed Button Effect below uses exactly scale(0.97), plus a slight dip and a drop shadow that collapses into an inner shadow: 0.08s on the way down, a springy 0.4s on the way back up.

Watch out for: Scale shrinks the label too, and 3% of a full-width mobile button is a lot of pixels. On wide buttons, use a smaller scale or a 1px to 2px translate instead.

2. GitHub Primer: no animation on press: Best for dense product UI where people click all day

Platform: Primer React (GitHub's design system), MIT

GitHub's buttons do not animate the press at all, and you can read the decision in the source. In Primer React's ButtonBase styles, colour, fill, background and border transition over 80ms on cubic-bezier(0.65, 0, 0.35, 1), but the :active rule sets transition: none, so the pressed colour appears on the very frame the pointer goes down.

That is the right call for buttons people press hundreds of times a day, like Merge, Comment and Approve. Any delay on press feels like lag, and an instant colour change feels like the click landed. Use it as your default, then add motion only where the moment deserves it.

Watch out for: Instant feedback only works if the pressed colour is clearly different from the hover colour. If the two are close, a quick click shows nothing at all. Check rest, hover and pressed side by side.

3. Josh W. Comeau's 3D button: Best for playful brands and the one big CTA on a page

Platform: Pure CSS tutorial, three spans inside a button

Live from our directory: 3D Pushable Button. Copy the HTML and CSS →

Josh W. Comeau's Building a Magical 3D Button is the best-known press animation on the web. The button stacks three layers: a blurred shadow, a darker edge and the front face. At rest the face sits 4px up, on hover it rises to 6px, on press it drops to 2px, and the shadow moves the opposite way each time.

The timing is the lesson. The press takes 34ms, about two frames at 60fps, so it feels instant. The rise on hover takes 250ms on cubic-bezier(0.3, 0.7, 0.4, 1.5), which overshoots into a small bounce, and the return after release is a slow 600ms. He also hides the focus ring after mouse clicks with :focus:not(:focus-visible) while keeping it for keyboard users.

Our 3D Pushable Button below is our pure-CSS version of the same layered technique, with the same 34ms press, and adds the reduced-motion guard the tutorial does not cover.

Watch out for: The moving layers get will-change: transform, which keeps each on its own GPU layer. Fine for one hero button, wasteful for a page of twenty. And add a prefers-reduced-motion block yourself.

4. Material Web's ripple: Best for Material design systems and Android-style web apps

Platform: Material Web (Lit web components), Apache 2.0

Live from our directory: Ripple Click Button. Copy the HTML and CSS →

The ripple is Material Design's signature press. In Material Web's source, the md-ripple element starts a circle 20% of the control's larger side at the point you pressed, then grows it over 450ms while drifting it to the centre, so it covers the whole control. The pressed layer fades in over 105ms to 12% opacity and fades out over 375ms, and the ripple stays at least 225ms even on a very quick click, so it is always seen. With no pointer position to start from, it starts in the centre.

Touch gets special care: md-ripple waits 150ms after a touch begins before it shows the press, so a finger that is starting a scroll does not light up every button it crosses.

Our Ripple Click Button below is a pure-CSS take. Because CSS cannot read where you clicked, the circle always grows from the centre, over 0.6s while the button is held, and fades as you let go.

Watch out for: Outside a Material look, a ripple on small icon buttons or busy surfaces reads as noise. Material pairs it with a hover state layer and a focus ring; it is not meant to carry the feedback alone.

5. React Aria's ripple button: Best for React apps that want the ripple from the touch point

Platform: React Aria Components + Tailwind CSS (Adobe)

Adobe's React Aria docs include a ripple example that does what CSS alone cannot: it starts the ripple where you actually pressed. The onPress handler stores e.x and e.y, a 32px white circle at 60% opacity is placed there, and a 600ms linear keyframe grows and fades it. Keying the element to the coordinates remounts it on each press, so rapid clicks each get a fresh ripple, and onAnimationEnd removes it.

The real win is the base. React Aria's Button already handles keyboard, touch and screen readers properly, so the animation sits on top of solid behaviour instead of on a div with a click handler.

Watch out for: The example has no prefers-reduced-motion check, and 600ms linear feels slow next to Material's eased timing. Add the guard and try a shorter ease-out curve.

6. Motion's whileTap: Best for React apps that want spring physics on press

Platform: React + Motion (formerly Framer Motion), MIT

Live from our directory: Spring Press Button. Copy the HTML and CSS →

Motion makes press feedback one prop: whileTap={{ scale: 0.9 }} on a motion.button animates while the pointer is down and springs back on release. The gesture docs note that a tap only counts if it ends on the same element (otherwise onTapCancel fires), and that pressing Enter on a focused element triggers whileTap too, so keyboard users get the same feedback.

Springs earn their keep on the release: they settle from wherever the button happens to be, so a tap cut short halfway still returns naturally. Our Spring Press Button below gets a similar feel in pure CSS: it squashes to scale(1.1, 0.8) in 0.15s on press and springs back over 0.9s through a linear() easing sampled from a spring.

Watch out for: Motion ignores the reduced-motion setting by default. Set reducedMotion="user" on MotionConfig to drop transform animations for people who ask. Also, 0.9 is a big squeeze for everyday buttons; 0.95 to 0.97 is calmer.

7. Ladda by Hakim El Hattab: Best for form submit buttons that wait on a server

Platform: JavaScript + CSS, MIT, npm install ladda

Live from our directory: Submit, Loading, Success Button. Copy the HTML and CSS →

Hakim El Hattab's Ladda put the loading indicator inside the button that started the action, so the feedback appears exactly where the person is looking. It has a dozen ways for the spinner to arrive: expand left, right, up or down, contract, contract overlay, zoom in, zoom out, and slide left, right, up or down. The expand-right and contract styles can also show a built-in progress bar.

The pattern it popularised is now standard: on click, disable the button, swap the label for a spinner, then swap the spinner for the result. Our Submit, Loading, Success Button below plays that whole sequence in pure CSS: the pill shrinks into a spinning ring, then grows back green and draws a check.

Watch out for: The Ladda repository has not been updated since December 2021. And our demo runs on a timer for show; in a real form, drive each state from the actual request and announce the result in an aria-live region.

GitHub ↗

8. Codrops Progress Button Styles: Best for downloads and uploads that report real progress

Platform: HTML, CSS and JavaScript, Codrops licence (2013)

Live from our directory: Download Button with Progress. Copy the HTML and CSS →

Codrops' Progress Button Styles turn the button itself into the progress bar. The flat styles fill the button as the work advances; the 3D styles rotate the button in perspective so the progress shows on one side, like a box turning to show its edge.

It is a good answer for anything that takes more than a couple of seconds. The person clicked here, so the progress stays here, and nothing else on the page needs to move. Our Download Button with Progress below is a pure-CSS version: the button darkens into a track, fills left to right while a percentage counts up, then shows a check.

Watch out for: A fill that finishes before the work does teaches people to ignore it. Only show a percentage you can actually measure, and use an indeterminate spinner when you cannot. The Codrops code is from 2013, so treat it as a reference rather than a dependency.

9. Codrops Particle Effects for Buttons: Best for one-way actions like send or delete

Platform: JavaScript + anime.js (Codrops, 2018)

Luis Manuel's Particle Effects for Buttons, published on Codrops in 2018, makes a button disintegrate into particles when clicked, and integrate back when the action is undone. It is a small library on top of anime.js: new Particles('.button').disintegrate() draws a canvas behind the button and breaks it apart from one side.

The variety comes from a few options: circle, rectangle or triangle particles, filled or stroked, the side it starts from, a particle count coefficient and an oscillation amount, with a default duration of 1000ms. Use it when the button's job ends on click, such as a Send button that must not be pressed twice. The button disappearing is the confirmation.

Watch out for: Once the button is gone, keyboard focus has nowhere to land. Move focus to the result or the next step, and skip the effect entirely under prefers-reduced-motion.

10. mo.js Burst: Best for small celebratory bursts on icon buttons

Platform: JavaScript motion library, MIT

Live from our directory: Clap Button. Copy the HTML and CSS →

mo.js, the motion graphics library by Oleg Solomko (legomushroom), has a Burst module built for click effects: it arranges a ring of particles around a point and sends them outward. count sets how many particles there are, radius sets how far the ring spreads, and children passes shape, fill, duration and delay to every particle.

Bursts work because they are short and centred on the thing you touched. Fire one at the click point and the press feels like it released a little energy. Our Clap Button below is a pure-CSS take: each press tips the hand, and each release sends a +1 floating up with a burst of short lines.

Watch out for: mo.js is a whole library for a few hundred milliseconds of effect. If a burst is all you need, a few pseudo-elements or a tiny canvas will do it for far less code.

mo.js home ↗

11. canvas-confetti by Kiril Vatev: Best for rare big moments like Publish, Upgrade or Launch

Platform: JavaScript, ISC licence, npm canvas-confetti

canvas-confetti is the go-to library for a celebration on click. A single confetti() call fires a burst from the middle of the screen, and the options shape it: particleCount (50 by default), spread (45 degrees by default), origin, angle, gravity, colours and shapes. Set origin to the button's position and the confetti comes out of the thing that was pressed.

It also has the most useful accessibility option of any library here: disableForReducedMotion skips the confetti entirely for people who ask their device for less motion.

Watch out for: disableForReducedMotion defaults to false, so pass true every time. And confetti on every click turns a celebration into noise; save it for the end of something that took effort. For quieter ways to say done, see our success animation examples.

12. Our Hold to Confirm Button: Best for destructive actions that need a deliberate press

Platform: Motions directory, free, pure CSS Ours

Live from our directory: Hold to Confirm Button. Copy the HTML and CSS →

Hold the button down and a fill sweeps across it over 1.4s, turning the label white as it passes; keep holding and it flips to a confirmed state. Let go early and the fill drains back in 0.45s. The whole thing runs on :active, so the animation needs no JavaScript.

A hold is a better guard than a confirm dialog for things people do often but must never do by accident, like deleting a draft or ending a call. The time it takes is the confirmation.

Watch out for: Holding :active is awkward from a keyboard and impossible for some assistive tech, so give those users a second path, such as a normal button with a confirm step. Trigger the real action from your code, not from the animation finishing.

13. Our 3D Press Tilt Button: Best for playful hero CTAs and game-style interfaces

Platform: Motions directory, free, pure CSS Ours

Live from our directory: 3D Press Tilt Button. Copy the HTML and CSS →

The button tips toward wherever you press it, like a card pushed down at one corner. Nine invisible zones sit over the face; each one, while :active, sets --x and --y through a sibling selector, and the face rotates up to 14 degrees in perspective. Press the middle and it simply sinks to 94%.

It goes down in 0.12s and springs back over 0.5s with a little overshoot, so it behaves like a physical object. Browse the rest of our pressable buttons in the Buttons directory, or describe your own to the creator.

Watch out for: Tilted text is harder to read, so keep the label short, and keep this off buttons people press many times in a row. It belongs where pressing is part of the fun.

How to choose a button click animation

Three things decide it: how often the button is pressed, what happens after the press, and who is pressing it.

Match it to how often it is pressed

Buttons pressed all day get instant feedback and nothing else: an instant colour change like Primer, or a 0.97 scale like Emil Kowalski's tip. Buttons pressed a few times a session can carry a 3D push or a ripple. Bursts and confetti are for buttons pressed once, at the end of something. The rarer the press, the more the animation can say.

Match it to what happens next

If the result is instant, the press animation is the whole story. If the button waits on a server, turn it into its own loading state, the way Ladda and the Submit, Loading, Success Button do, and disable it so it cannot be pressed twice. If the action cannot be undone, slow it down on purpose with a hold to confirm. For likes and other toggles, see our like button animation examples.

Match it to the input and the motion settings

Keep the press itself under about 100ms, then spend 200ms to 600ms on the release. Make sure keyboard presses get feedback too: Motion's whileTap fires on Enter, and an instant :active colour covers the Space key for free. Under prefers-reduced-motion, keep the colour change and the result state, and drop the scaling, tilting, particles and confetti.

Make your buttons answer back today

Give every button an instant pressed colour and a small scale on :active, then pick one moment in your product that deserves more: a loading-to-success morph on the main form, a hold on the delete button, or a burst on the one action people celebrate. Copy the CSS from our Buttons directory and set the colour with the custom property.

If you want a press nobody else has, describe it to the creator. It asks up to four quick questions while it works and hands back production-ready CSS keyframes.

A prompt to start from
Make a click animation for a Save button in a web app.
On press, scale to 0.97 in 60ms with ease-out; on release, spring back over 300ms with a slight overshoot.
After the click, swap the label for a small spinner, then for a check mark and the word Saved, then reset after 1.5s.
Animate only transform and opacity, and keep keyboard focus on the button throughout.
Under prefers-reduced-motion, skip the scale and spinner motion and just swap the labels.

Make your own with AI →

Common questions

How do you make a button click animation in CSS?

Add a transition to the button, then change it in :active, for example transform: scale(0.97). Make the press fast (under about 100ms) and the release slower. For states that last after the click, such as loading or done, toggle a class with JavaScript, or use a hidden checkbox the way the demos in our Buttons directory do.

What is the ripple effect on a button?

It is Material Design's press feedback: a translucent circle that grows from the point you pressed until it covers the button, then fades. Material Web grows it over 450ms and keeps it at least 225ms so quick clicks still show it. CSS alone can only grow it from the centre; starting it at the click point needs JavaScript.

How long should a button click animation last?

The press should feel instant: GitHub's Primer uses no transition at all on press, Josh W. Comeau's 3D button uses 34ms. The release and any follow-up state can take 200ms to 600ms. Anything slower than that on press reads as lag.

Can you animate a button click without JavaScript?

Yes, for anything that happens while the button is held, since :active covers press scale, ripples, tilts and hold-to-confirm. A state that persists after the click needs either a hidden checkbox or JavaScript, and in a real form it should follow the actual request, not a timer.

Should click animations respect prefers-reduced-motion?

Yes. Keep the colour change and the final state, since they carry the meaning, and remove scaling, tilting, particles and confetti. canvas-confetti has a disableForReducedMotion option, and Motion's MotionConfig takes reducedMotion="user"; neither is on by default.

Sources