07 / CSS / 2026-04-14
CTA Gallery
20 ways to earn a click. Pick a favorite and I will wire it site-wide.
How it works
// 01 DPI and pixel density
Each variant is one component file with a dedicated set of styles. Interaction logic falls into three categories: pure CSS (hover, focus, active, transitions), CSS plus pointer tracking (magnetic, tilt), and CSS plus timing (hold-to-confirm, progress fill). Pure CSS variants are the cheapest and rely on `:hover`, `:focus-visible`, and `transition` properties to animate between states.
// 02 Bleed and cut tolerance
Pointer-tracking variants bind `pointermove` and `pointerleave` listeners to each button. On move, the handler reads the event coordinates relative to the button's bounding rect, calculates an offset from center, and writes it to CSS custom properties. The stylesheet reads those properties and applies a transform. The separation of concerns (JavaScript writes math, CSS reads math) keeps the render on the GPU's compositor instead of triggering layout.
// 03 Safe zone and trim accuracy
Timing-based variants (hold-to-confirm, progress fill) use `requestAnimationFrame` to drive a progress value from zero to one over a target duration. The progress is written to a CSS custom property each frame and the stylesheet renders the visual fill. Release before the fill completes cancels the animation. Pressing space does the same thing as a click-and-hold for keyboard users.
// why this exists
interaction range without reaching for a library
A call-to-action button is the highest-pressure piece of UI on any page. It is where the visitor decides whether to keep reading or do the thing. Twenty variations on that button, each built with plain CSS and a few lines of JavaScript, each demonstrating a different interaction pattern. Magnetic hover, ripple effect, arrow slide, fill sweep, shimmer pass, glitch on focus, hold-to-confirm, skeleton-to-solid, underline reveal, and a dozen more. Click through the gallery to feel them.
The point is not that more motion equals a better CTA. The opposite is true. A subtle hover cue on a clean button usually outperforms an animated monster that feels like it is trying too hard. What this gallery proves is range. If a client wants a calm, confident button, the vocabulary is here. If the brand supports something playful, that is here too. The gallery is a menu, not a recommendation.
Every variation is self-contained. Each button is a single component with its own CSS and, where needed, a small amount of JavaScript for pointer tracking or timing logic. No dependencies beyond React. No shared state. The magnetic hover effect, for example, is about 30 lines of code and uses `pointermove` to calculate the offset from the button center and apply a translate transform proportional to that offset. It feels expensive and costs nothing.
Accessibility runs through the whole gallery. Every button has visible focus states (not just hover), keyboard interaction that matches the mouse interaction, and `prefers-reduced-motion` support that strips the motion while preserving the color and state changes. The hold-to-confirm variant has keyboard-equivalent behavior (space to start, space to confirm) because a button that only works with a mouse is a broken button.
The gallery also serves as a live comp for client work. When a client is choosing a button direction, I can send the link, they click through and land on a favorite, and we wire that specific variant site-wide in an afternoon. That is faster and more concrete than a slide deck of screenshots.
Frequently asked questions
Why 20 CTA variations?
To show range. Brand identities differ, audiences differ, contexts differ. A calm B2B tool does not want the same button as a cozy game. The gallery is a menu so the right option is already drawn.
Is more motion better for conversion?
No. Excessive motion on a CTA often hurts conversion because it reads as desperate or distracting. A subtle hover cue on a clean button usually outperforms a heavily animated one. Motion is a seasoning, not a main course.
How do magnetic buttons work?
A pointermove listener reads the cursor position relative to the button and writes an offset to a CSS custom property. The button translates by that offset, which makes it feel like the button is pulled toward the cursor.
Do these work on touch devices?
Yes, though some hover-dependent variants degrade gracefully (the magnetic effect has no analogue on touch, so it disables itself on coarse pointers). All variants keep a clear pressed state for taps.
How do I pick a CTA for my own site?
Start with the least aggressive option that still feels on-brand. Measure conversion. If the numbers are fine, do not add motion for the sake of it. If conversion is low, a stronger variant might help, but the copy and placement almost always matter more than the motion.
What about accessibility for animated buttons?
Every variant here honors prefers-reduced-motion, has a visible focus state independent of hover, and keeps keyboard interaction equivalent to mouse interaction. A button that only works with a mouse is broken.
Can I use these directly in my project?
Yes. Each variant is a single component file with self-contained styles. Copy the file, import the component, you're done. No dependencies beyond React.
How long does it take to wire the chosen variant site-wide?
Usually an afternoon. The gallery component is already structured for drop-in replacement, so swapping the site's Button component for the chosen variant is a single import change plus style reconciliation.
Why not just use a library like Framer Motion?
A library is the right call for complex animation systems. For button interactions, the native CSS and a few lines of vanilla JavaScript produce identical results with zero bundle cost. Adding a library for a button is overkill.