20 Tailwind CSS Animated Buttons

10 Pure Tailwind10 Tailwind + JSPublished

20 Tailwind CSS animated buttons for form submit and add-to-cart flows, download progress and loading state machines, gradient and neon CTAs, hamburger and toggle switches, and social share rows — spanning ripple and shine sweeps, liquid fills, 3D keycap presses, jelly bounces, text flips, checkmark draws, and cursor-aware reveals. Scoped under .tab-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.

Related20 CSS Animated Buttons36 CSS Button Hover Effects20 Tailwind CSS Image Hover Effects36 Tailwind CSS Button Hover Effects

Premium upgradePartner: Untitled UI

Affiliate link

Building a whole Tailwind app, not just this component?

You just browsed 20 Tailwind animated buttons. Untitled UI Pro unlocks 5,000+ components and sections and 250+ page examples for React, styled with Tailwind CSS v4. One licence is a one-time $349 with lifetime updates; teams start at $999.

Get Untitled UI Pro

Untitled UI React. We may earn a commission, at no extra cost to you.

01. Tailwind Neon Glow Button with Pulsing Halo (opens in a new tab)

Published

Pure Tailwind
A neon-lit CTA with a breathing glow halo, ported to a single class list. The port's win is --animate-tab01-pulse: the whole animation shorthand is declared once as a token, so the markup says animate-tab01-pulse and the hover override says hover:[animation-play-state:paused] — the trigger reads where the button is, not three rules away.
Newsletter

Enjoying these demos? Get new ones by email.

A short email when a new collection ships. Unsubscribe any time.

at

Almost there: check your inbox to confirm.Manage

07. Tailwind Moving Gradient CTA Button (opens in a new tab)

Published

Pure Tailwind
An infinite colour-cycling CTA: one oversized gradient, one background-position loop. The port is nearly a transcription — bg-[linear-gradient(...)] bg-[size:300%_300%] animate-tab07-shift — and the notable part is how the reduced-motion fallback becomes a one-utility override instead of a duplicated rule.
Newsletter

Enjoying these demos? Get new ones by email.

A short email when a new collection ships. Unsubscribe any time.

at

Almost there: check your inbox to confirm.Manage

FAQ

Frequently asked questions

How do I define the custom keyframe animations these buttons use in Tailwind?
Tailwind ships no neon-pulse or jelly-squash animation, so each port registers its own in the theme layer. In v4 you add a keyframes block plus an --animate-* token inside @theme, then play it with a utility like animate-[--animate-halo] or a named animate-halo. The neon glow (Demo 01) and the seven-step jelly bounce (Demo 09) both work this way — the whole squash timeline lives in one @keyframes and the utility just runs it. In v3 you instead extend theme.extend.keyframes and theme.extend.animation in the config file. The payoff the ports keep pointing at is that once the token exists, duration, easing and iteration all stay as utilities on the element, so the entire animation is still readable in the class list rather than hidden in a stylesheet.
How does the ripple button work with no JavaScript?
Demo 03 puts the whole effect in one pseudo-element. A before: layer sits centred at zero size with before:scale-0 and a radial background; the active: variant flips it to active:before:scale-150 so pressing the button expands the circle from the middle, then it fades back. Because it keys off :active — the press state the browser already tracks — there is no click handler, no appended node and nothing to clean up afterward. The trade-off is that a true Material ripple originates at the pointer coordinate, while this one always grows from the button centre, since CSS cannot read where you clicked without JS. For most CTAs that difference is invisible, and in return you get a ripple that survives being copied into any framework untouched, with no lifecycle code to port alongside it.
Why isn't my peer-checked toggle knob moving?
Demo 17 drives three switches with zero JavaScript, and the usual bug is variant-target confusion. peer-checked: only styles an element that comes after the checked input in the same parent, and only when that input carries the bare peer class — put peer on the wrong node, or the knob before the input, and nothing moves. The track uses peer-checked:bg-* while the knob uses peer-checked:translate-x-full. The has-checked: variant instead reads state from a descendant, so the styled element must be the parent of the input, not a sibling — a common reason a converted demo breaks. Keep the real checkbox in the DOM with sr-only rather than hidden: a display-none input drops out of the tab order and the switch stops being keyboard-operable, which fails the control silently.
How does the hamburger button animate from bars to an X accessibly?
Demo 14 animates the three bars into an × by keying the transforms off aria-expanded instead of a cosmetic .is-open class. Tailwind exposes attribute state through the aria-expanded: variant, so aria-expanded:rotate-45 on the top bar, aria-expanded:-rotate-45 on the bottom, and aria-expanded:opacity-0 on the middle bar produce the X. The single line of JS just flips the attribute's value on the <button>. This is the accessible pattern: the same attribute a screen reader announces — collapsed versus expanded — is the one the animation reads, so the visuals and the semantics can never drift apart. Give the control an aria-label since it has no visible text, and set aria-expanded on the trigger itself, not on the menu it controls, or assistive tech announces the wrong element's state.
How does the moving gradient CTA loop without a visible seam?
Demo 07 paints one gradient far wider than the button — think bg-[length:200%_auto] — then animates background-position from one end to the other in an infinite loop. The seam is avoided by making the gradient's first and last colour stops identical, so the wrap-around is continuous and reads as endless rather than snapping. Animating background-position is relatively cheap because it does not reflow geometry, but it is not compositor-only the way transform is, so on a page carrying dozens of these you can notice paint cost and should measure. Keep the motion on the background rather than on a huge animated box-shadow, and wrap the utility in motion-reduce:animate-none so the colour churn stops for anyone who has asked the system for less motion. That one variant is the difference between a polished CTA and a distracting one.
Why does my 3D text-flip button look flat instead of rotating in space?
Demos 10 and 05 need a 3D context the flat utilities do not create by default. The rotation only reads as depth if the parent establishes perspective — in v4 that is the perspective-* utilities or an arbitrary [perspective:600px], applied to the wrapper and not to the rotating face. The two label faces then need [transform-style:preserve-3d] on their shared parent and backface-hidden on each, so the reversed side does not bleed through mid-flip. The incoming message is pre-rotated with [transform:rotateX(90deg)] and swings to zero on hover as the front face pivots away. Forget the perspective and every rotateX collapses into a vertical squash — the flat look you are describing. The keycap press in Demo 05 is unrelated here: it is just a translate-y plus a shrinking shadow and needs no perspective at all.
When should I avoid these animated buttons, and how do I respect reduced-motion?
Reach for the plain version whenever the button is doing real work under load. A submit or loading state machine (Demos 12 and 18) must never signal success through motion alone — pair the checkmark draw or spinner with a text change and an aria-live region, because a drawn stroke announces nothing to a screen reader and the user is left guessing whether the action completed. Skip the infinite loops — neon pulse, moving gradient, beacon rings (Demo 11) — in dense dashboards or long lists, where a dozen always-running animations tax paint and pull attention off the content. And every continuous or hover-triggered effect here should carry motion-reduce:animate-none, plus a motion-reduce:transform-none where a transform is the whole point, so the roughly one user in twenty who sets the reduced-motion preference gets a still, usable control instead of vestibular churn.
Why is my slide-fill or shine-sweep effect spilling outside the button's rounded corners?
The directional fills (Demo 02), liquid rise (06), gloss streak (08) and cursor-aware reveal (20) all animate a pseudo-element that begins larger than or offset from the button. Two utilities keep it contained: relative on the button so the absolutely-positioned before: or after: layer anchors to it, and overflow-hidden so the part of that layer outside the box — and outside the rounded corners — is clipped. Miss overflow-hidden and the wave or streak bleeds past the radius into a hard rectangle. The other half of the bug is stacking order: give the moving layer z-0 or a negative index and the label relative z-10, otherwise the fill paints over the text and the button looks blank mid-hover. The ghost button additionally sets its reveal origin from a CSS variable a pointer handler updates — the single line of JS it actually needs.

Related collections

20 CSS Animated Buttons preview

20 CSS Animated Buttons

20 hand-coded CSS animated buttons — neon glow, ripple, 3D press, liquid fill, jelly bounce, shine sweep, animated border, moving gradient CTA, text flip, submit success state, add-to-cart progress, download icon, hamburger-to-close, toggle switch, loading spinner inside button, next/prev arrow nav, and ghost button background reveal. Half pure CSS, half lightweight JS for production interactions.

11 CSS animation-timeline Demos preview

11 CSS animation-timeline Demos

11 pure CSS animation-timeline demos built on the W3C scroll() and view() spec — reading progress bar, view-timeline fade & reveal, shrinking sticky header, stacking cards, horizontal scroll section, parallax hero, container shadow indicators, reverse-scroll columns, text scrim reveal, cover card to sticky header, and image zoom / clip-path wipe. Zero JS, off the main thread, Core Web Vitals safe. Copy-paste ready.

25 CSS Background Animations preview

25 CSS Background Animations

25 hand-coded CSS background animations for SaaS hero sections, developer tool documentation, agency portfolios, gaming and Web3 landing pages, seasonal e-commerce campaigns, and dark-mode dashboards. Covers full-screen gradient shifts, animated mesh gradients, aurora borealis glow, cursor spotlight follow, interactive water ripples, dot grid and diagonal stripe patterns, SVG grain and noise texture overlays, topographic contour lines, layered SVG waves, plasma and lava lamp fluids, bokeh light blur, particle constellation networks, matrix digital rain, synthwave 3D perspective grids, starfield warp speed, CRT scanline overlays, fog and mist drift, morphing organic blobs, floating glassmorphism orbs, halftone dot patterns, starry night skies, floating geometric shapes, rising bubbles, and falling snow. 20 are pure CSS with zero JavaScript; the 5 that need it use vanilla canvas or pointer tracking with no dependencies. Every background animates compositor-only properties so it never blocks the main thread, is scoped under a .bga-NN prefix for no-collision pasting, guards prefers-reduced-motion, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.

Search CodeFronts

Loading…