Css
How to create a radial menu in CSS
25 September 2026 · 14 min read
Creating intuitive and visually appealing navigation is crucial for a positive user experience. Radial menus, also known as circular menus or pie menus, offer a stylish and space-saving alternative to traditional dropdown or horizontal menus. They are particularly effective in applications and websites where users frequently access a core set of actions. This guide provides a comprehensive walkthrough on how to create a radial menu using CSS, empowering you to enhance your web projects with this dynamic navigation element.
Understanding the Structure of a Radial Menu
A radial menu typically consists of a central trigger button and a set of menu items that fan out in a circular arrangement when the trigger is activated. The menu items can be icons, text labels, or a combination of both. Understanding the underlying HTML structure is the first step towards creating a functional and visually appealing radial menu. This structure commonly involves nested lists or divs to represent the menu and its items.
The central trigger element acts as the focal point for interaction. Clicking or hovering over this element toggles the visibility of the menu items. Each menu item is positioned around the trigger using CSS transforms and transitions, creating the characteristic radial layout. This approach ensures a clean and organized presentation, especially beneficial on smaller screens or when screen real estate is limited.
Basic HTML Setup
Start by creating the basic HTML for your radial menu. This will involve a container element to hold the entire menu structure, a central trigger button, and individual menu items. You can use an unordered list (
) with list items (- ) for the menu items. Here’s a sample structure: html
</div>This simple structure provides the foundation for styling and positioning the menu elements using CSS. Remember to use semantic HTML for accessibility and better code organization. Using a button element for the trigger, for example, ensures keyboard accessibility.
Styling the Radial Menu with CSS
--------------------------------
The magic of the radial menu happens in the CSS. You'll use CSS properties like `transform: rotate()` and `transition` to create the circular arrangement and smooth animations. Here's how you can style the menu:
css .radial-menu { position: relative; / To position menu items relative to the trigger / } .menu-items { position: absolute; list-style: none; / Remove default bullet points / opacity: 0; / Initially hide the menu / transform: scale(0); / Initially scale down the menu / transition: all 0.3s ease; / Smooth transition for opening/closing / } .menu-trigger:hover + .menu-items, .menu-items:hover { opacity: 1; transform: scale(1); } .menu-items li { display: inline-block; transform-origin: 50% 100%; / Center the rotation point / } / Position each menu item / .menu-items li:nth-child(1) { transform: rotate(0deg); } .menu-items li:nth-child(2) { transform: rotate(90deg); } .menu-items li:nth-child(3) { transform: rotate(180deg); } .menu-items li:nth-child(4) { transform: rotate(270deg); } This CSS code positions the menu items around the trigger button using the `rotate()` transform. The `transition` property ensures a smooth opening and closing animation. You can adjust the timings and easing functions to fine-tune the animation. This structured approach allows for precise control over the placement and appearance of each menu item within the circular layout.
Advanced Techniques and Customization
-------------------------------------
Once you have the basic radial menu working, you can enhance it with more advanced techniques. Consider adding submenus for deeper navigation, using icons for visual appeal, or implementing dynamic content loading. JavaScript can be used to handle more complex interactions, such as toggling the menu's visibility on click or implementing custom animations. Libraries like GreenSock Animation Platform (GSAP) can provide even more sophisticated animation control.
Consider incorporating user interface enhancements such as tooltips or hover effects to improve usability. Implementing these features can significantly enrich the user experience by providing clear visual feedback and guiding user interactions. Explore different visual styles and layouts to align the menu with your overall website design. Experiment with different color schemes, iconography, and typography to create a visually cohesive and engaging menu.
- Use CSS preprocessors like SASS or LESS for easier styling and maintenance.
- Consider using JavaScript libraries for complex animations and interactions.
1. Plan the menu structure and content.
2. Create the HTML structure for the menu.
3. Style the menu using CSS.
4. Enhance with JavaScript for advanced features.
For instance, a photography portfolio website might use a radial menu to showcase different categories of photographs, like portraits, landscapes, and macro photography. Each menu item could lead to a corresponding gallery page. Learn more about CSS transforms on [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/transform).
[Learn more about Radial Menus.](https://courthousezoological.com/n7sqp6kh?key=e6dd02bc5dbf461b97a9da08df84d31c)**Featured Snippet:** Radial menus are created using HTML for structure and CSS for styling and positioning. The key CSS properties are `transform: rotate()` and `transition` to arrange and animate the menu items around a central trigger.
\[Infographic illustrating the structure and CSS styling of a radial menu\]
Frequently Asked Questions (FAQs)
---------------------------------
**Q: What are the advantages of using a radial menu?**
A: Radial menus offer a compact and visually engaging way to present a set of options, especially suitable for touch interfaces and applications requiring quick access to common actions.
**Q: How can I customize the appearance of the radial menu?**
A: CSS provides extensive customization options. You can adjust colors, sizes, icons, fonts, and animation styles to match your design aesthetic.
Radial menus provide a fresh and engaging way to enhance navigation on your website. By mastering the CSS techniques outlined in this guide, you can create visually compelling and user-friendly menus that elevate the overall user experience. From understanding the basic structure to implementing advanced customizations, you now have the tools to integrate radial menus effectively. Don't hesitate to experiment and explore different variations to find the perfect radial menu design for your projects. Check out these additional resources for further exploration: [CSS-Tricks](https://css-tricks.com), [Smashing Magazine](https://www.smashingmagazine.com/), and [W3Schools](https://www.w3schools.com/).
**Question & Answer :**
How do I create a menu which looks like so:

[Link to PSD](http://dribbble.com/shots/732391-Tooltip-PSD)
I don't want to use the PSD images. I would prefer using icons from some package like [FontAwesome](http://fortawesome.github.com/Font-Awesome/) and have the backgrounds/css generated in CSS.
A version of the menu that is using the PSD to to generate images of the tooltip and then using it can be found [here](http://theearlcarlson.com/experiments/amTooltip/).
*Almost 3 years later, I finally made the time to revisit this and post an improved version. You can still view the original answer at the end for reference.*
*While SVG may be the better choice, especially today, my goal with this was to keep it just HTML and CSS, no JS, no SVG, no images (other than the background on the root element).*
***[2015 demo](http://codepen.io/thebabydino/pen/aOWeLa?editors=010)***
=======================================================================
### Screenshots
Chrome 43:

Firefox 38:

IE 11:

### Code
The HTML is pretty simple. I'm using the checkbox hack to reveal/ hide the menu.
```
<input type='checkbox' id='t'/> <label for='t'>✰</label> <ul> <li><a href='#'>☀</a></li> <li><a href='#'>☃</a></li> <li><a href='#'>☁</a></li> </ul>
```
I'm using Sass to keep this logical and make it easier to change things if needed. Heavily commented.
```
$d: 2em; // diameter of central round button $r: 16em; // radius of menu $n: 3; // must match number of list items in DOM $exp: 3em; // menu item height $tip: .75em; // dimension of tip on middle menu item $w: .5em; // width of ends $cover-dim: 2*($r - $exp); // dimension of the link cover $angle: 15deg; // angle for a menu item $skew-angle: 90deg - $angle; // how much to skew a menu item to $angle $scale-factor: cos($skew-angle); // correction factor - see vimeo.com/98137613 from min 15 $off-angle: .125deg; // offset angle so we have a little space between menu items // don't show the actual checkbox input { transform: translate(-100vw); // move offscreen visibility: hidden; // avoid paint } // change state of menu to revealed on checking the checkbox input:checked ~ ul { transform: scale(1); opacity: .999; // ease out back from easings.net/#easeOutBack transition: .5s cubic-bezier(0.175, 0.885, 0.32, 1.275); } // position everything absolutely such that their left bottom corner // is in the middle of the screen label, ul, li { position: absolute; left: 50%; bottom: 50%; } // visual candy styles label, a { color: #858596; font: 700 1em/ #{$d} sans-serif; text-align: center; text-shadow: 0 1px 1px #6c6f7e; cursor: pointer; } label { z-index: 2; // place it above the menu which has z-index: 1 margin: -$d/2; // position correction such that it's right in the middle width: $d; height: $d; border-radius: 50%; box-shadow: 0 0 1px 1px white, 0 .125em .25em #876366, 0 .125em .5em #876366; background: radial-gradient(#d4c7c5, #e5e1dd); } ul { z-index: 1; margin: -$r + $exp + 1.5*$d 0; // position correction padding: 0; list-style: none; transform-origin: 50% (-$r + $exp); transform: scale(.001); // initial state: scaled down to invisible will-change: transform; // better perf on transitioning transform opacity: .001; // initial state: transparent filter: drop-shadow(0 .125em .25em #847c77) drop-shadow(0 .125em .5em #847c77); // ease in back, also from easings.net transition: .5s cubic-bezier(0.6, -0.28, 0.735, 0.045); // menu ends &:before, &:after { position: absolute; margin: -$exp (-$w/2); width: $w; height: $exp; transform-origin: 50% 100%; background: linear-gradient(#ddd, #c9c4bf); content: ''; } &:before { border-radius: $w 0 0 $w; transform: rotate(-.5*$n*$angle) translate(-$w/2, -$r + $exp); box-shadow: inset 1px 0 1px #eee; } &:after { border-radius: 0 $w $w 0; transform: rotate(.5*$n*$angle) translate($w/2, -$r + $exp); box-shadow: inset -1px 0 1px #eee; } } li { overflow: hidden; width: $r; height: $r; transform-origin: 0 100%; @for $i from 0 to $n { &:nth-child(#{$i + 1}) { $curr-angle: $i*$angle + ($i + .5)*$off-angle - .5*$n*($angle + $off-angle); // make each list item a rhombus rotated around its bottom left corner // see explanation from minute 33:10 youtube.com/watch?v=ehjoh_MmE9A transform: rotate($curr-angle) skewY(-$skew-angle) scaleX($scale-factor); // add tip for the item n the middle, just a rotated square @if $i == ($n - 1)/2 { a:after { position: absolute; top: $exp; left: 50%; margin: -$tip/2; width: $tip; height: $tip; transform: rotate(45deg); box-shadow: inset -1px -1px 1px #eee; background: linear-gradient(-45deg, #bbb, #c9c4bf 50%); content: ''; } } } } a, &:before { margin: 0 (-$r); width: 2*$r; height: 2*$r; border-radius: 50%; } &:before, &:after { position: absolute; border-radius: 50%; // undo distorting transforms from menu item (parent li) transform: scaleX(1/$scale-factor) skewY($skew-angle); content: ''; } // actual background of the arched menu items &:before { box-shadow: inset 0 0 1px 1px #fff, inset 0 0 $exp #ebe7e2, inset 0 0 1px ($exp - .0625em) #c9c4bf, inset 0 0 0 $exp #dcdcdc; } // cover to prevent click action in between the star and menu items &:after { top: 100%; left: 0; margin: -$cover-dim/2; width: $cover-dim; height: $cover-dim; border-radius: 50%; } } a { display: block; // undo distorting transforms from menu item and rotate into right position transform: scaleX(1/$scale-factor) skewY($skew-angle) rotate($angle/2); line-height: $exp; text-align: center; text-decoration: none; }
```
<div class="snippet" data-babel="false" data-console="false" data-hide="true" data-lang="js"><div class="snippet-code snippet-currently-hidden"> ```
html { overflow: hidden; background: url(http://i.imgur.com/AeFfmwL.jpg); } input { /* move offscreen */ -webkit-transform: translate(-100vw); -ms-transform: translate(-100vw); transform: translate(-100vw); /* avoid paint */ visibility: hidden; } input:checked ~ ul { -webkit-transform: scale(1); -ms-transform: scale(1); transform: scale(1); opacity: .999; /* ease out back from easings.net */ -webkit-transition: 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); transition: 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); } label, ul, li { position: absolute; left: 50%; bottom: 50%; } label, a { color: #858596; font: 700 1em/ 2em sans-serif; text-align: center; text-shadow: 0 1px 1px #6c6f7e; cursor: pointer; } label { z-index: 2; margin: -1em; width: 2em; height: 2em; border-radius: 50%; box-shadow: 0 0 1px 1px white, 0 .125em .25em #876366, 0 .125em .5em #876366; background: #d3d3d3; background: -webkit-radial-gradient(#d4c7c5, #e5e1dd); background: radial-gradient(#d4c7c5, #e5e1dd); } ul { z-index: 1; margin: -10em 0; padding: 0; list-style: none; -webkit-transform-origin: 50% -13em; -ms-transform-origin: 50% -13em; transform-origin: 50% -13em; -webkit-transform: scale(0.001); -ms-transform: scale(0.001); transform: scale(0.001); /* for improved perf on transitioning transform * https://twitter.com/paul_irish/status/608492121734193152 */ will-change: transform; opacity: .001; -webkit-filter: drop-shadow(0 0.125em 0.25em #847c77); filter: drop-shadow(0 0.125em 0.25em #847c77); -webkit-transition: 0.5s cubic-bezier(0.6, -0.28, 0.735, 0.045); transition: 0.5s cubic-bezier(0.6, -0.28, 0.735, 0.045); } ul:before, ul:after { position: absolute; margin: -3em -0.25em; width: 0.5em; height: 3em; -webkit-transform-origin: 50% 100%; -ms-transform-origin: 50% 100%; transform-origin: 50% 100%; background: #d3d3d3; background: -webkit-linear-gradient(#ddd, #c9c4bf); background: linear-gradient(#ddd, #c9c4bf); content: ''; } ul:before { border-radius: 0.5em 0 0 0.5em; -webkit-transform: rotate(-22.5deg) translate(-0.25em, -13em); -ms-transform: rotate(-22.5deg) translate(-0.25em, -13em); transform: rotate(-22.5deg) translate(-0.25em, -13em); box-shadow: inset 1px 0 1px #eee; } ul:after { border-radius: 0 0.5em 0.5em 0; -webkit-transform: rotate(22.5deg) translate(0.25em, -13em); -ms-transform: rotate(22.5deg) translate(0.25em, -13em); transform: rotate(22.5deg) translate(0.25em, -13em); box-shadow: inset -1px 0 1px #eee; } li { overflow: hidden; width: 16em; height: 16em; -webkit-transform-origin: 0 100%; -ms-transform-origin: 0 100%; transform-origin: 0 100%; } li:nth-child(1) { -webkit-transform: rotate(-22.625deg) skewY(-75deg) scaleX(0.25882); -ms-transform: rotate(-22.625deg) skewY(-75deg) scaleX(0.25882); transform: rotate(-22.625deg) skewY(-75deg) scaleX(0.25882); } li:nth-child(2) { -webkit-transform: rotate(-7.5deg) skewY(-75deg) scaleX(0.25882); -ms-transform: rotate(-7.5deg) skewY(-75deg) scaleX(0.25882); transform: rotate(-7.5deg) skewY(-75deg) scaleX(0.25882); } li:nth-child(2) a:after { position: absolute; top: 3em; left: 50%; margin: -0.375em; width: 0.75em; height: 0.75em; -webkit-transform: rotate(45deg); -ms-transform: rotate(45deg); transform: rotate(45deg); box-shadow: inset -1px -1px 1px #eee; background: -webkit-linear-gradient(135deg, #bbb, #c9c4bf 50%); background: linear-gradient(-45deg, #bbb, #c9c4bf 50%); content: ''; } li:nth-child(3) { -webkit-transform: rotate(7.625deg) skewY(-75deg) scaleX(0.25882); -ms-transform: rotate(7.625deg) skewY(-75deg) scaleX(0.25882); transform: rotate(7.625deg) skewY(-75deg) scaleX(0.25882); } li a, li:before { margin: 0 -16em; width: 32em; height: 32em; border-radius: 50%; } li:before, li:after { position: absolute; border-radius: 50%; -webkit-transform: scaleX(3.8637) skewY(75deg); -ms-transform: scaleX(3.8637) skewY(75deg); transform: scaleX(3.8637) skewY(75deg); content: ''; } li:before { box-shadow: inset 0 0 1px 1px #fff, inset 0 0 3em #ebe7e2, inset 0 0 1px 2.9375em #c9c4bf, inset 0 0 0 3em #dcdcdc; } li:after { top: 100%; left: 0; margin: -13em; width: 26em; height: 26em; border-radius: 50%; } a { display: block; -webkit-transform: scaleX(3.8637) skewY(75deg) rotate(7.5deg); -ms-transform: scaleX(3.8637) skewY(75deg) rotate(7.5deg); transform: scaleX(3.8637) skewY(75deg) rotate(7.5deg); line-height: 3em; text-align: center; text-decoration: none; }
```
```
<input type='checkbox' id='t'/> <label for='t'>✰</label> <ul> <li><a href='#'>☀</a></li> <li><a href='#'>☃</a></li> <li><a href='#'>☁</a></li> </ul>
```
</div> </div>---
Original answer
---------------
My attempt at doing something of the kind with pure CSS:
[*demo*](http://dabblet.com/gist/3979221/cb69c5e8ccf0745fff2c94b47b27b108931f1a15)
==================================================================================
(click the star)
Works in Chrome, Firefox (a bit weirdish blur effect on hover), Opera (ends look smaller) & Safari (ends look smaller).
<div class="snippet" data-babel="false" data-console="false" data-hide="false" data-lang="js"><div class="snippet-code"> ```
* { margin: 0; padding: 0; } body { overflow: hidden; } /* generic styles for button & circular menu */ .ctrl { position: absolute; top: 70%; left: 50%; font: 1.5em/1.13 Verdana, sans-serif; transition: .5s; } /* generic link styles */ a.ctrl, .ctrl a { display: block; opacity: .56; background: #c9c9c9; color: #7a8092; text-align: center; text-decoration: none; text-shadow: 0 -1px dimgrey; } a.ctrl:hover, .ctrl a:hover, a.ctrl:focus, .ctrl a:focus { opacity: 1; } a.ctrl:focus, .ctrl a:focus { outline: none; } .button { z-index: 2; margin: -.625em; width: 1.25em; height: 1.25em; border-radius: 50%; box-shadow: 0 0 3px 1px white; } /* circular menu */ .tip { z-index: 1; /**outline: dotted 1px white;/**/ margin: -5em; width: 10em; height: 10em; transform: scale(.001); list-style: none; opacity: 0; } /* the ends of the menu */ .tip:before, .tip:after { position: absolute; top: 34.3%; width: .5em; height: 14%; opacity: .56; background: #c9c9c9; content: ''; } .tip:before { left: 5.4%; border-radius: .25em 0 0 .25em; box-shadow: -1px 0 1px dimgrey, inset 1px 0 1px white, inset -1px 0 1px grey, inset 0 1px 1px white, inset 0 -1px 1px white; transform: rotate(-75deg); } .tip:after { right: 5.4%; border-radius: 0 .25em .25em 0; box-shadow: 1px 0 1px dimgrey, inset -1px 0 1px white, inset 1px 0 1px grey, inset 0 1px 1px white, inset 0 -1px 1px white; transform: rotate(75deg); } /* make the menu appear on click */ .button:focus + .tip { transform: scale(1); opacity: 1; } /* slices of the circular menu */ .slice { overflow: hidden; position: absolute; /**outline: dotted 1px yellow;/**/ width: 50%; height: 50%; transform-origin: 100% 100%; } /* * rotate each slice at the right angle = (A/2)° + (k - (n+1)/2)*A° * where A is the angle of 1 slice (30° in this case) * k is the number of the slice (in {1,2,3,4,5} here) * and n is the number of slices (5 in this case) * formula works for odd number of slices (n odd) * for even number of slices (n even) the rotation angle is (k - n/2)*A° * * after rotating, skew on Y by 90°-A°; here A° = the angle for 1 slice = 30° */ .slice:first-child { transform: rotate(-45deg) skewY(60deg); } .slice:nth-child(2) { transform: rotate(-15deg) skewY(60deg); } .slice:nth-child(3) { transform: rotate(15deg) skewY(60deg); } .slice:nth-child(4) { transform: rotate(45deg) skewY(60deg); } .slice:last-child { transform: rotate(75deg) skewY(60deg); } /* covers for the inner part of the links so there's no hover trigger between star button & menu links; give them a red background to see them */ .slice:after { position: absolute; top: 32%; left: 32%; width: 136%; height: 136%; border-radius: 50%; /* "unskew" = skew by minus the same angle by which parent was skewed */ transform: skewY(-60deg); content: ''; } /* menu links */ .slice a { width: 200%; height: 200%; border-radius: 50%; box-shadow: 0 0 3px dimgrey, inset 0 0 4px white; /* "unskew" & rotate by -A°/2 */ transform: skewY(-60deg) rotate(-15deg); background: /* lateral separators */ linear-gradient(75deg, transparent 50%, grey 50%, transparent 54%) no-repeat 36.5% 0, linear-gradient(-75deg, transparent 50%, grey 50%, transparent 54%) no-repeat 63.5% 0, /* make sure inner part is transparent */ radial-gradient(rgba(127,127,127,0) 49%, rgba(255,255,255,.7) 51%, #c9c9c9 52%); background-size: 15% 15%, 15% 15%, cover; line-height: 1.4; } /* arrow for middle link */ .slice:nth-child(3) a:after { position: absolute; top: 13%; left: 50%; margin: -.25em; width: .5em; height: .5em; box-shadow: 2px 2px 2px white; transform: rotate(45deg); background: linear-gradient(-45deg, #c9c9c9 50%, transparent 50%); content: ''; }
```
```
<a class='button ctrl' href='#' tabindex='1'>★</a> <ul class='tip ctrl'> <li class='slice'><a href='#'>✦</a></li> <li class='slice'><a href='#'>✿</a></li> <li class='slice'><a href='#'>✵</a></li> <li class='slice'><a href='#'>✪</a></li> <li class='slice'><a href='#'>☀</a></li> </ul>
```
</div> </div>