What You'll Build

In this tutorial you'll create a magic animated motion button that uses nothing but HTML and CSS. At rest it looks like a simple, elegant pill-shaped link with a single word label. The moment you hover over it, the button scales up and five floating 3D geometric objects — a cone, a torus, an icosahedron, a sphere, and a control pad icon — materialize around it from hidden positions, each traveling to a unique final orbit with smooth transitions and individual rotation angles.

Beneath the button, a blurred gradient glow shadow fades in at the same moment, making the entire element appear to lift off the page. Two of the shapes also carry a subtle filter: blur() value, creating a convincing depth-of-field effect where some objects look closer and others feel slightly out of focus in the background.

This is a polished example of how CSS transforms, transitions, opacity, and filter effects can be layered together without a single line of JavaScript to produce a visually rich, interactive UI component.

💡 Who Is This For? This project suits beginners who understand basic CSS positioning and want to push into transforms and transitions with a real, showstopper component, as well as intermediate developers looking for a clean reference on layering multiple animated elements from a single hover trigger.

Key Features of This Animated Motion Button

💊
Pill-Shaped Button
A soft border-radius: 3rem creates the classic pill shape that scales up to 1.3× on hover for a satisfying pop.
🔮
5 Floating 3D Shapes
Cone, torus, icosahedron, sphere, and a control pad orbit the button from hidden starting positions using transform: translate() rotate().
Gradient Glow Shadow
A ::after pseudo-element with a blurred linear-gradient and filter: blur(12px) creates a soft halo beneath the button on hover.
🌫️
Depth-of-Field Blur
The cone and icosahedron carry a slight filter: blur() value, making them appear on a different depth plane from the sharp foreground shapes.
🎯
Pointer Events Control
pointer-events: none on all images ensures the floating shapes never intercept mouse events — hover always registers on the button itself.
🪶
Zero JavaScript
The entire effect — scale, shape reveal, glow, and depth — is driven exclusively by the CSS :hover pseudo-class and transitions.
🎨
Google Fonts Typography
Montserrat Alternates Bold gives the button label a distinctive, geometric character that complements the 3D shape aesthetic perfectly.
GPU-Composited Animation
Every animated property — transform, opacity — runs on the GPU compositing layer, delivering silky-smooth 60 fps transitions on any device.

Full Source Code (Free)

The project is split across two files: index.html for the markup and styles.css for all the animation logic. The HTML is a single anchor tag containing the text label and five image assets. All motion, hover effects, and glow are handled entirely in CSS. Use the tabs below to browse each file.

HTML — index.html
CSS — styles.css

🔓 Full Source Code unlocks in

05

Hosted on GitHub Gist — free, no sign-up required

💡 Quick Start: Drop index.html and styles.css into the same folder alongside the img/ folder containing all five shape PNG files. Open in any modern browser and hover the button to see the magic.

Project Assets & Images

This project relies on five PNG image files stored in an img/ folder. Each one represents a distinct 3D geometric object rendered against a transparent background, allowing them to float freely over the button without visible boundaries. Together they form the animated constellation that orbits the label on hover.

img/cone.png

A rendered 3D cone shape. Positioned to the upper-left of the button and rotated 55° in its resting state. On hover it shifts further left and upward while scaling up slightly. A filter: blur(0.5px) pushes it visually into the background.

Suggested Alt: "Floating 3D cone shape orbiting an animated CSS button"

img/torus.png

A rendered 3D donut/ring (torus) shape. The largest of the floating objects at 38px wide. It sits above the button label and travels upward on hover, acting as a visual crown for the animation. Remains fully sharp — no blur applied.

Suggested Alt: "3D torus ring floating above animated CSS motion button"

img/icosahedron.png

A 3D twenty-faced polygon. Positioned to the right side of the button with a filter: blur(0.9px) — the most out-of-focus shape, implying it sits furthest in the background. Moves to the upper-right on hover.

Suggested Alt: "Blurred 3D icosahedron shape in CSS button hover animation"

img/sphere.png

A 3D sphere rendered at 30px. Placed below the button in its resting state and drops further down on hover while remaining crisp and in-focus, providing a strong visual anchor at the bottom of the effect.

Suggested Alt: "3D sphere floating below animated CSS hover button"

img/control.png

The largest asset at 90px — a rendered 3D game controller or control pad icon. It anchors to the lower-right of the button and stays in that zone on hover, scaling up in place. Its large size adds visual weight and balances the lighter geometric shapes.

Suggested Alt: "3D control pad icon in animated magic CSS button effect"

💡 Asset Tip: All five images use transparent PNG backgrounds, which is essential for the floating effect to work. If you substitute your own images, make sure they're also exported as transparent PNGs or SVGs — opaque backgrounds will cover the button text and break the illusion.

How It Works — Step by Step

Here's a breakdown of the eight core techniques behind this magic animated motion button, from the base markup structure down to the depth-of-field blur layering.

01

Anchor Tag as the Button Container

The button is an <a> tag with class="button" and position: relative. This establishes the positioning context for all five absolutely positioned shape images inside it, so each one's inset: 0 and margin: auto starts them centered inside the button before the hover transforms move them outward.

02

Pill Shape, Color, and Base Transition

The button gets background-color: var(--first-color) — a soft blue-grey hsl value — with border-radius: 3rem for the pill shape and padding: .9rem 2.20rem for comfortable proportions. A single transition: .4s applies to all properties, so the scale and any color changes animate smoothly on hover.

03

The Glow Shadow with ::after

A ::after pseudo-element sits behind the button at z-index: -1. It's sized to 80% of the button's width and 40% of its height, positioned just below via bottom: -4px, and carries a blurred linear-gradient using filter: blur(12px). By default its opacity is 0; on hover it transitions to 1, making the glow appear to emanate from beneath the lifting button.

04

Hiding the Shapes with Opacity and Pointer Events

All img elements inside .button share the rules: position: absolute; inset: 0; margin: auto; pointer-events: none; opacity: 0; transition: .6s. The pointer-events: none is critical — it ensures that even when shapes are visible on hover, mouse events pass through them to the parent anchor, keeping the :hover state active.

05

Pre-Positioning Each Shape at Rest

Each shape class gets an individual transform: translate() rotate() rule that defines its hidden starting position. For example, the cone starts at translate(-25px, -6px) rotate(55deg) and the control starts at translate(60px, 30px) rotate(10deg). These resting positions are the same on hover — which means the animation is the reveal traveling from opacity 0 to 1 plus a simultaneous scale change.

06

Scaling the Button on Hover

.button:hover applies transform: scale(1.3). Because the images are children positioned relative to the button, they scale with it — so the entire constellation of shapes expands outward naturally when the button grows, without needing separate scale rules on each image.

07

Revealing and Moving Shapes on Hover

.button:hover img sets opacity: 1 to make all shapes visible. Then each individual class gets an updated transform value on hover — for example, the torus moves from translate(7px, -14px) to translate(7px, -32px) scale(1.1), traveling upward and slightly enlarging. The .6s transition on the base image rule ensures this travel animates smoothly.

08

Depth of Field via CSS Filter Blur

The cone receives filter: blur(0.5px) at its base state and the icosahedron gets filter: blur(0.9px). These values persist on hover. Because the torus, sphere, and control are unblurred, the eye reads the blurred shapes as being further away, creating a layered 3D scene from what is actually a flat 2D element.

⚠️ z-index and Stacking Context: The .button__text span uses position: relative; z-index: 10 to stay above the images. If you add more children to the button, make sure they don't accidentally create a new stacking context that pushes the label behind the shape images.

Customization Ideas

The motion button is structured so that virtually every visual decision is easy to override. Here are practical ways to make it your own.

  • Change the color theme — Update --first-color in the :root block and adjust the hsl values inside the ::after gradient to match. A warm amber, neon green, or deep purple all work beautifully with the 3D shape aesthetic.
  • Replace the shape images — Swap any of the five PNGs for your own transparent PNG or SVG assets. Emojis rendered as images, product icons, or mascots all work. Adjust each class's width and transform values to match your new asset sizes.
  • Adjust travel distance — Increase the translate() values on each shape's hover state to make objects fly further from the button for a more explosive effect, or reduce them for a subtler float.
  • Change the button label — The span contains a single word "play". Replace it with any call-to-action text. Keep it short — one or two words — so the pill proportions remain visually balanced.
  • Add more shapes — The pattern is infinitely extensible. Add more img tags inside .button with new classes, set their resting transform positions, and define where they travel on :hover.
  • Increase the blur intensity — Raise filter: blur(12px) on the ::after glow for a softer, dreamier halo, or lower it for a tighter, crisper shadow.
  • Make it a real button element — Swap the <a> tag for a <button> element and add a click event listener to trigger any action. All the CSS classes remain identical.
💡 Tip: Because the entire effect is CSS-only, you can drop this button into any framework — React, Vue, Svelte, or plain HTML — with no dependency concerns. Just copy the markup and stylesheet rules.

Browser Compatibility

Every CSS property used in this project — transform, transition, opacity, filter, CSS custom properties, and position: absolute — has been universally supported across all modern browsers for several years.

Browser CSS Transforms & Transitions CSS Filter (blur) Overall Support
Chrome / Edge Yes Yes Full
Firefox Yes Yes Full
Safari (macOS & iOS) Yes Yes Full
Samsung Internet Yes Yes Full
Internet Explorer 11 Partial No Limited
💡 Note: filter: blur() is not supported in Internet Explorer 11, which means the depth-of-field blur on the cone and icosahedron will simply not render — the shapes will still appear and animate, just without the blur. IE11 is effectively retired and not a practical target for projects in 2026.

Performance & Responsiveness

Despite the dramatic visual effect, this button has an extremely small performance footprint because every animation property is handled by the browser's GPU compositing layer.

  • GPU-friendly properties: transform and opacity drive every shape's motion and reveal. These are composited on the GPU without triggering layout recalculation or repaint cycles, keeping the animation consistently smooth even on mobile hardware.
  • Zero JavaScript overhead: With no event listeners, no DOM manipulation, and no animation libraries, the scripting cost of this component is literally zero.
  • Optimized image loading: The five PNG assets are small, transparent-background files. Once they're downloaded (typically on first page load) they're cached and don't affect subsequent hover performance.
  • Touch-screen behavior: On touch devices, the :hover state typically fires on tap-and-hold or on a tap that navigates the link. For a touch-optimized experience consider pairing this with a :focus-visible or JavaScript-driven class toggle to control the reveal on tap.
  • Viewport centering: The body uses display: grid; place-items: center; height: 100vh to center the button perfectly in any viewport, from mobile to ultrawide desktop. No media queries are required for the core button layout.
💡 Key Takeaway: Animating transform and opacity instead of layout-triggering properties like width, top, or left is the single most impactful habit for building smooth CSS animations. This project is a clean example of that principle applied across five simultaneous animated elements.

Frequently Asked Questions

How do I make images appear around a button on hover using CSS?
Position the images absolutely inside the button with position: absolute; inset: 0; margin: auto and set opacity: 0 plus a transition on the base state. On .button:hover img, set opacity: 1 and update each image's transform value to move it to its final position. The transition handles the smooth travel between states automatically.
What is the CSS technique used to create the glowing shadow below the button?
A ::after pseudo-element with z-index: -1 sits behind the button. It has a linear-gradient background matching the button's color and a filter: blur(12px) that spreads it into a soft halo. Its opacity starts at 0 and transitions to 1 on :hover, making the glow appear to emanate from beneath the button as it scales up.
How does the depth-of-field (blur) effect work on the 3D shapes?
filter: blur() is applied directly on specific shape images — the cone gets blur(0.5px) and the icosahedron gets blur(0.9px). Because the torus, sphere, and control remain unblurred, the human eye reads the blurred shapes as being on a different depth plane, creating a convincing 3D layering effect from a completely flat 2D element.
Why is pointer-events: none used on the images inside the button?
The shape images are decorative overlays and should never intercept mouse events. Without pointer-events: none, hovering over a shape image would count as leaving the button, causing the :hover state to toggle on and off rapidly as the cursor moves across visible shapes. Setting it on every image ensures the parent anchor always receives all mouse events.
Can I replace the 3D shape images with my own custom images or icons?
Absolutely. Each image uses a dedicated class (.button__cone, .button__torus, etc.) with its own width and transform values. Swap the src attribute to any transparent PNG or SVG, then update that class's width and the translate() values in both the resting and hover states to match your new asset's dimensions and desired orbit.
How do I change the button's color theme?
The button's background is controlled by --first-color in the :root block (default: hsl(217, 75%, 80%)). Update this value to any color. The ::after glow gradient also references similar hsl values inline — update both gradient color stops to match your new --first-color for a cohesive result.
Is this animated button accessible for keyboard users?
Because the button is an <a> tag, it receives keyboard focus naturally when tabbing. The :hover state does not automatically apply to focused elements in all browsers. For full keyboard accessibility, duplicate the hover rules under a :focus-visible selector so that tabbing to the button also reveals the floating shapes for sighted keyboard users.
Does this animated button work without JavaScript?
Yes — this is a 100% JavaScript-free project. The entire hover animation, shape reveal, glow shadow, and scale effect are driven exclusively by CSS :hover pseudo-class selectors combined with transition, transform, and opacity rules. This makes it extremely lightweight and embeddable in any project or framework without adding scripting dependencies.

Conclusion

This magic animated motion button is a great demonstration of how much personality pure CSS can inject into a single UI element. By combining absolute positioning, opacity transitions, transform-based movement, filter blur for depth, and a blurred pseudo-element glow, you get a production-ready button that feels genuinely magical — and the JavaScript line count stays at zero.

The techniques here transfer directly to other components: any element can have decorative children that appear on :hover using this same pattern of absolute positioning plus opacity and transform toggling. Product cards, navigation links, and call-to-action sections all benefit from this kind of layered hover choreography.

Want to keep exploring animated buttons and CSS micro-interactions? Check out the Animated Floating Action Button Menu for a FAB that expands into a full action list, or the Animated Button with Star Effects for particle-style CSS button animations.

Found this useful? Explore more HTML CSS projects in the sidebar or browse all projects in the collection.