How to create a wheel-shaped menu using CSS and HTML

- Andrés Cruz

ES En español

How to create a wheel-shaped menu using CSS and HTML

Radial or wheel-type menus are an excellent design solution for optimizing space in modern interfaces, especially on mobile devices. Originally inspired by the Floating Action Button (FAB) concept from Google's Material Design, these elements allow for grouping multiple actions into a single focal point, unfolding them in an elegant and animated way.

In this guide, we will explore how to create different styles of circular menus using exclusively CSS and HTML, with a minimal touch of JavaScript to handle click interactions. We will see everything from minimalist designs to more advanced options with images.

Fundamentals of a Circular Menu

The logic behind a wheel-type options menu is simple yet powerful:

  1. Initial State: All menu options are hidden and positioned exactly beneath the center button (the "trigger").
  2. Activation: Upon clicking or hovering, we apply a CSS transition that moves each option to a specific coordinate using transform: translate(x, y).
  3. Animation: We use transition so that the movement is fluid, creating that "fan" or "wheel" effect.

Example of a dropdown circular menu

1. Radial Menu with Click Interaction

This first design is the most faithful to the Material Design concept. The menu unfolds only when the user decides to interact with it.

The JavaScript: For this example, we use a simple script that adds or removes the .open class from the main container.

// Handler to open/close the menudocument.querySelectorAll('.toggle-btn').forEach(btn => { btn.addEventListener('click', () => { const parent = btn.closest('.filter-btn'); parent.classList.toggle('open'); });});// Close when an option is selecteddocument.querySelectorAll('.filter-btn a').forEach(link => { link.addEventListener('click', () => { link.closest('.filter-btn').classList.remove('open'); });});

The Main CSS: We define the button base and transitions. It is important to note the use of cubic-bezier to give it a more natural bounce effect.

.filter-btn { position: absolute; width: 40px; height: 40px; transition: all 0.4s ease;}.filter-btn a { position: absolute; background: #DE5513; border-radius: 50%; width: 40px; height: 40px; line-height: 40px; text-align: center; color: #fff; z-index: 1; transition: all .4s cubic-bezier(.68, -0.55, .265, 1.55); box-shadow: 0 4px 10px rgba(0,0,0,0.3);}/* Positioning of options when opened */.filter-btn.open a:nth-child(1) { transform: translate(0, -60px); }.filter-btn.open a:nth-child(2) { transform: translate(-55px, -35px); }.filter-btn.open a:nth-child(3) { transform: translate(55px, -35px); }

As we can see, it is a basic design where primarily the shape, color, and size of the button are defined; in addition to other aspects such as the transitions and position of the options that form the wheel/circular menu defined with filter-btn a.

And the second one to show the wheel/circular button already deployed:

Wheel-type options menu 1

.filter-btn.open a:nth-child(1) { transform: translate(0,-55px);}.filter-btn.open a:nth-child(2) { transform: translate(-50px,-34px);}.filter-btn.open a:nth-child(3) { transform: translate(50px,-34px);}.filter-btn.open a:nth-child(4) { transform: translate(50px,20px);}.filter-btn.open a:nth-child(5) { transform: translate(-50px,20px);}.filter-btn.open a:nth-child(6) { transform: translate(0,55px);}.filter-btn.open span.toggle-btn.ion-android-funnel { background-color: #AC3D07; }.filter-btn.open .ion-android-funnel:before { content: "\f2d7";}

Finally we obtain:

2. Variant with Expansive Background ("After" Effect)

We can add an extra layer of sophistication by using the ::after pseudo-element to create a circular background that expands behind the icons, improving the visibility of the options.

Wheel-type options menu 2
.filter-btn::after { content: ''; width: 170px; height: 170px; background-color: rgba(222, 85, 19, 0.9); position: absolute; top: -65px; right: -65px; border-radius: 50%; transform: scale(0); transition: transform 0.3s ease-in-out; z-index: 0;}.filter-btn.open::after { transform: scale(1);}

Finally we obtain:

3. Hover-based Circular Menu (No JavaScript)

If you prefer a lighter and faster solution for desktop, you can use the :hover event. This eliminates the need for JavaScript, although you must be careful with the experience on touch devices.

Wheel-type options menu 3

In this model, the menu is rotated 180 degrees by default and returns to its original position when hovering:

.menu ul { transform: rotate(180deg) translateY(-2em); transition: 1s all ease; opacity: 0;}.menu:hover ul { transform: rotate(0deg) translateY(-1em); opacity: 1;}

4. Radial Menu with Images and Clippings

This is one of the most creative designs. Instead of simple icons, we use images that form a perfect circumference when unfolded. Each image is a sector of the circle.

.wrap a:nth-child(1) { border-radius: 40vmin 0 0 0; transform-origin: 110% 110%; background-image: url('https://farm3.staticflickr.com/2827/10384422264_d9c7299146.jpg'); background-size: cover;}

As we can see, (among several things) we define the size for each item whose height is 1.4em:

.menu li {/* ... */ height: 1.4em; opacity: 0; z-index: -1;}

It is also specified that all options are hidden via the opacity property set to zero; once we position ourselves over the circular menu (the middle circle) we change the opacity to one so that it becomes visible:

.menu:hover li { opacity: 1;}

Additionally, we activate the rotation of the menu so that it returns to its original position:

.menu:hover ul { transform: rotate(0deg) translateY(-1em);}

By default, the circular menu is rotated by 180 degrees:

.menu ul { transform: rotate(180deg) translateY(-2em); transition: 1s all;}

And from here we can understand how the transition of the menu elements works when they are shown, performing a displacement of about 180 degrees; finally, as a fundamental element, knowing the composition of each item in the menu:

.menu li:nth-of-type(1) { transform: rotate(-90deg); position: absolute; left: -1.2em; top: -4.2em;}.menu li:nth-of-type(2) { transform: rotate(-45deg); position: absolute; left: 2em; top: -3em;}.menu li:nth-of-type(3) { position: absolute; left: 3.4em; top: 0.3em;}.menu li:nth-of-type(4) { transform: rotate(45deg); position: absolute; left: 2em; top: 3.7em;}.menu li:nth-of-type(5) { transform: rotate(90deg); position: absolute; left: -1.2em; top: 5em;}

5. Wheel/circular menu based on images and hover event

The next menu we will see is of a type that displays a set of images clipped through a circumference:

This menu has a similar operation to the previous one in that it shows the fan of options when hovering the cursor over the visible section of the menu (hover event), which is a circumference with the typical hamburger menu icon; the rest of the options are hidden via the opacity property set to zero:

.wrap{ position:relative; width:80vmin; height:80vmin; margin:0 auto; background:inherit; transform:scale(0.2) translatez(0px); opacity:0; transition:transform .5s, opacity .5s;}

Once we position ourselves over the hamburger menu, all options are shown:

span:hover + .wrap, .wrap:hover{ transform:scale(.8) translateZ(0px); opacity:1;}

The images that are shown when positioning the cursor over the hamburger-type icon have the following definition:

a:nth-child(1){ border-radius:40vmin 0 0 0; transform-origin: 110% 110%; transition:transform .4s .15s;}a:nth-child(1) div{ background-image:url('https://farm3.staticflickr.com/2827/10384422264_d9c7299146.jpg');}a:nth-child(2){ border-radius:0 40vmin 0 0; left:52.5%; transform-origin: -10% 110%; transition:transform .4s .2s;}a:nth-child(2) div{ background-image:url('https://farm7.staticflickr.com/6083/6055581292_d94c2d90e3.jpg');}a:nth-child(3){ border-radius:0 0 0 40vmin; top:52.5%; transform-origin: 110% -10%; transition:transform .4s .25s;}

As we can see, the image is varied according to the link using the :nth-child property, which has also been a topic in previous entries:

The nth-child pseudo-class in CSS 

Finally, with the top and left properties, we shift the shapes that make up the deployed menu.

Modernization: CSS Variables and Accessibility

Use of CSS Variables

Instead of manually calculating each translate(x, y), you can use variables to control distance and angle, facilitating global changes:

.filter-btn { --distancia: 80px;}.open a:nth-child(1) { transform: translateY(calc(-1 * var(--distancia))); }

Accessibility (A11y)

Don't forget that these menus must be operable with the keyboard. Make sure to:

  • Use <button> tags for the main trigger.
  • Add aria-expanded="false" attributes that change to true via JS.
  • Ensure the color contrast between the icon and the background is sufficient (4.5:1 ratio).

Conclusion

Creating a wheel-type options menu with CSS and HTML not only improves your site's aesthetics but also offers an interactive and fluid user experience. Whether you choose a click-based or hover-based implementation, the key lies in the smart use of CSS transformations and transitions.

Learn to create modern radial and circular menus with CSS and HTML. Step-by-step guide with interaction examples (click/hover), variable usage, and accessibility features.

I agree to receive announcements of interest about this Blog.

Andrés Cruz

ES En español