Rotating menu effects rely on CSS3's transform property for rotation transformations, paired with @keyframes for custom animation keyframes, and can be triggered via CSS pseudo-classes or JavaScript interaction. Below are multiple common implementation approaches and working examples.
Core Implementation Concept
- HTML Structure: Create a wrapper container to hold all navigation items
- CSS Styling:
- Use
transform: rotate()to handle rotation transformations - Define custom animation frames with
@keyframes - Control animation duration and easing with
transitionoranimationproperties
- Use
- Interaction Trigger: Trigger rotation via CSS
:hoverfor simple use cases, or add JavaScript click handlers for toggleable interactive menus
Hover-Triggered Continuous Rotation Menu
This basic example starts rotating when the user hovers over the menu container:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hover Rotating Menu</title>
<style>
.rotate-menu-wrapper {
width: 220px;
height: 220px;
position: relative;
margin: 3rem auto;
border-radius: 50%;
background: #f5f5f5;
overflow: hidden;
}
.nav-item {
width: 100%;
height: 100%;
position: absolute;
inset: 0;
transition: transform 0.5s ease;
display: grid;
place-content: center;
font-size: 1.25rem;
color: #fff;
cursor: pointer;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.rotate-menu-wrapper:hover .nav-item {
animation: spin 2s linear infinite;
}
.nav-item:nth-child(1) { background: #ff6347; }
.nav-item:nth-child(2) { background: #4682b4; }
.nav-item:nth-child(3) { background: #32cd32; }
.nav-item:nth-child(4) { background: #ff8c00; }
</style>
</head>
<body>
<div class="rotate-menu-wrapper">
<div class="nav-item">Home</div>
<div class="nav-item">About</div>
<div class="nav-item">Blog</div>
<div class="nav-item">Contact</div>
</div>
</body>
</html>
This implementation uses a circular container with layered full-size navigation items. Each item gets a unique background color for visibility, and rotation is activated only when the user hovers over the container.
Toggleable Radial Rotating Menu
This is a production-friendly expandable circular menu with smooth staggered animations, built with Tailwind CSS and vanilla JavaScript:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Toggleable Rotating Menu</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
brand: '#4F46E5',
brandLight: '#818CF8',
},
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.radial-item-transform {
transform-origin: 100% 100%;
transition: transform 0.5s ease;
}
.radial-container {
transform-origin: center;
transition: transform 0.7s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.expanded .radial-container {
transform: rotate(360deg);
}
.toggle-icon {
transition: transform 0.5s ease;
}
.expanded .toggle-icon {
transform: rotate(135deg);
}
}
</style>
</head>
<body class="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center p-4">
<div class="relative">
<button id="menuToggle" class="w-16 h-16 rounded-full bg-brand text-white shadow-lg flex items-center justify-center z-50">
<span class="toggle-icon text-xl">
<i class="fa-solid fa-plus"></i>
</span>
</button>
<div id="radialMenu" class="absolute w-64 h-64 rounded-full radial-container">
<div class="menu-item absolute w-12 h-12 rounded-full bg-brandLight text-white shadow-md flex items-center justify-center radial-item-transform" style="transform: rotate(0deg) translate(80px) rotate(0deg);">
<a href="#" class="text-xl"><i class="fa-solid fa-house"></i></a>
</div>
<div class="menu-item absolute w-12 h-12 rounded-full bg-brandLight text-white shadow-md flex items-center justify-center radial-item-transform" style="transform: rotate(72deg) translate(80px) rotate(-72deg);">
<a href="#" class="text-xl"><i class="fa-solid fa-user"></i></a>
</div>
<div class="menu-item absolute w-12 h-12 rounded-full bg-brandLight text-white shadow-md flex items-center justify-center radial-item-transform" style="transform: rotate(144deg) translate(80px) rotate(-144deg);">
<a href="#" class="text-xl"><i class="fa-solid fa-gear"></i></a>
</div>
<div class="menu-item absolute w-12 h-12 rounded-full bg-brandLight text-white shadow-md flex items-center justify-center radial-item-transform" style="transform: rotate(216deg) translate(80px) rotate(-216deg);">
<a href="#" class="text-xl"><i class="fa-solid fa-bell"></i></a>
</div>
<div class="menu-item absolute w-12 h-12 rounded-full bg-brandLight text-white shadow-md flex items-center justify-center radial-item-transform" style="transform: rotate(288deg) translate(80px) rotate(-288deg);">
<a href="#" class="text-xl"><i class="fa-solid fa-envelope"></i></a>
</div>
</div>
</div>
<div class="absolute bottom-6 text-center text-slate-500 text-sm">
<p>Click center button to toggle menu</p>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const toggleBtn = document.getElementById('menuToggle');
const navItems = document.querySelectorAll('.menu-item');
const radialWrapper = document.getElementById('radialMenu');
let isExpanded = false;
toggleBtn.addEventListener('click', () => {
isExpanded = !isExpanded;
if (isExpanded) {
toggleBtn.classList.add('expanded');
radialWrapper.classList.add('expanded');
navItems.forEach((item, idx) => {
setTimeout(() => {
item.style.opacity = '1';
item.style.transform = `rotate(${idx * 72}deg) translate(80px) rotate(${-idx * 72}deg)`;
}, idx * 100);
});
} else {
toggleBtn.classList.remove('expanded');
radialWrapper.classList.remove('expanded');
navItems.forEach((item, idx) => {
setTimeout(() => {
item.style.opacity = '0';
item.style.transform = `rotate(0deg) translate(0) rotate(0deg)`;
}, (navItems.length - idx - 1) * 100);
});
}
});
// Initialize closed state
navItems.forEach(item => {
item.style.opacity = '0';
item.style.transform = 'rotate(0deg) translate(0) rotate(0deg)';
});
// Add hover interaction
navItems.forEach(item => {
item.addEventListener('mouseenter', () => {
item.classList.add('scale-110', 'bg-brand');
item.classList.remove('bg-brandLight');
});
item.addEventListener('mouseleave', () => {
item.classList.remove('scale-110', 'bg-brand');
item.classList.add('bg-brandLight');
});
});
});
</script>
</body>
</html>
Key features of this implementation include evenly spaced circular menu items, staggered cascade animation when opening/closing, hover micro-interactions, and full responsiveness acros all screen sizes.
Auto-Rotating Navigation Bar
This example creates an automatical rotating horziontal navigation bar, with an option for smoother animation via requestAnimationFrame:
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<nav class="top-nav">
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">Services</a></li>
<li><a href="#">Products</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
<script src="script.js"></script>
</body>
</html>
CSS (styles.css):
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Arial, sans-serif;
}
.top-nav {
position: relative;
width: 100%;
height: 50px;
background: #333;
margin-top: 50px;
transition: transform 0.2s ease-in-out;
}
.top-nav ul {
list-style: none;
display: flex;
justify-content: center;
}
.top-nav li {
flex: 1;
text-align: center;
}
.top-nav li a {
display: block;
color: #fff;
padding: 14px 16px;
text-decoration: none;
}
.top-nav li a:hover {
background: #111;
}
Basic JavaScript (script.js with setInterval):
window.addEventListener('load', () => {
const nav = document.querySelector('.top-nav');
let currentRotation = 0;
setInterval(() => {
currentRotation += 10;
nav.style.transform = `rotate(${currentRotation}deg)`;
if (currentRotation >= 360) currentRotation = 0;
}, 100);
});
Smoother alternative with requestAnimationFrame:
window.addEventListener('load', () => {
const nav = document.querySelector('.top-nav');
let currentRotation = 0;
function rotateNav() {
currentRotation += 0.5;
nav.style.transform = `rotate(${currentRotation}deg)`;
if (currentRotation >= 360) currentRotation = 0;
requestAnimationFrame(rotateNav);
}
rotateNav();
});
Hover-Triggered Individual Link Rotation
This example rotates individual navigation links when the user hovers over them, with cross-browser vendor prefixes for wider compatibility:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hover Rotate Links</title>
<style type="text/css">
ul {
margin-top: 30px;
list-style: none;
line-height: 25px;
font-family: Arial, sans-serif;
font-weight: bold;
display: flex;
gap: 8px;
padding: 0 10px;
}
li {
width: 120px;
border: 1px solid #ccc;
background-color: #e4e4e4;
}
li:hover {
background-color: #999;
}
a {
display: block;
padding: 5px 10px;
color: #333;
text-decoration: none;
transition: all 0.3s ease;
}
a:hover {
background-color: #ff9900;
color: #FFF;
-webkit-transform: rotate(30deg);
-moz-transform: rotate(30deg);
-o-transform: rotate(30deg);
-ms-transform: rotate(30deg);
transform: rotate(30deg);
}
</style>
</head>
<body>
<ul>
<li><a href="#">HTML5</a></li>
<li><a href="#">CSS3</a></li>
<li><a href="#">React</a></li>
<li><a href="#">NodeJS</a></li>
</ul>
</body>
</html>