Categories: CSSScriptWeb Design

Build Modern Accessible Circular Menus with Pure CSS – Radial Menu

This is a modern, customizable, space-saving radial (circular) navigation menu built using HTML & CSS. No JavaScript needed.

It transforms traditional hamburger menu interactions into elegant circular layouts that expand from a central toggle button.

Each menu item positions itself along a perfect circle using CSS transforms and custom properties.

Features

    Sponsored
  • Fully responsive design: Adapts to different screen sizes automatically
  • Smooth animations: Uses CSS transitions for fluid opening and closing effects
  • Accessibility focused: Includes proper ARIA labels and keyboard navigation support
  • Customizable styling: CSS custom properties for easy theming and sizing
  • Performance optimized: Uses transform3d for hardware acceleration

How to use it:

1. Add the HTML structure to your page. The radial menu uses a checkbox input to control the menu state without JavaScript:

  • input type="checkbox": This is the hidden state machine. Its :checked status determines if the menu is open or closed.
  • <label for="toggle">: This is the visible hamburger menu button that controls the checkbox.
  • ul.menu-list: The container for the menu items.
  • <li style="--i: 0">: Each menu item needs an inline custom property --i that represents its index, starting from 0. This is critical for the CSS to position each item correctly around the circle.
<nav class="menu" aria-label="Main Menu">
  <input
    type="checkbox"
    id="toggle"
    name="cb"
    class="visually-hidden"
  />
  <label
    for="toggle"
    aria-controls="menu-list"
    aria-label="Open/Close Menu"
  >
    <span class="burger" aria-hidden="true">
      <span></span>
      <span></span>
      <span></span>
    </span>
  </label>
  <ul class="menu-list">
    <li style="--i: 0">
      <a href="#" aria-label="Home">
        <span class="icon">
          <img
            src="/path/to/icon.svg"
            alt=""
            aria-hidden="true"
          />
        </span>
      </a>
    </li>
    <li style="--i: 1">
      <a href="#" aria-label="Account">
        <span class="icon">
          <img
            src="/path/to/icon.svg"
            alt=""
            aria-hidden="true"
          />
        </span>
      </a>
    </li>
    <li style="--i: 2">
      <a href="#" aria-label="Like">
        <span class="icon">
          <img
            src="/path/to/icon.svg"
            alt=""
            aria-hidden="true"
          />
        </span>
      </a>
    </li>
    <li style="--i: 3">
      <a href="#" aria-label="Settings">
        <span class="icon">
          <img
            src="/path/to/icon.svg"
            alt=""
            aria-hidden="true"
          />
        </span>
      </a>
    </li>
    <li style="--i: 4">
      <a href="#" aria-label="Logout">
        <span class="icon">
          <img
            src="/path/to/icon.svg"
            alt=""
            aria-hidden="true"
          />
        </span>
      </a>
    </li>
  </ul>
</nav>

2. The main CSS styles for the radial menu. Here, you can quickly modify the menu’s appearance. For instance, if you need 7 menu items instead of 5, you just change --count: 5; to --count: 7; and add the corresponding <li> elements to your HTML. The CSS will handle the rest.

:root {
  --count: 5;
  --radius: 6rem;
  --toggle-size: 4rem;
  --item-size: 3.5rem;
  --gap: 0.4rem;
  --bg: #f4f5f7;
  --fg: #2c2f36;
  --muted: rgba(0, 0, 0, 0.05);
  --shadow: 0 6px 18px rgba(0, 0, 0, 0.05);
  --duration: 200ms;
  --offset: -0.25turn; 
}

nav.menu {
  position: relative;
  width: max(var(--toggle-size), var(--radius) * 2 + var(--item-size));
  height: max(var(--toggle-size), var(--radius) * 2 + var(--item-size));
  display: grid;
  place-content: center;
}

nav label {
  width: var(--toggle-size);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  background-color: #fff;
  border-radius: 50%;
  box-shadow: var(--shadow);
  cursor: pointer;
  transition: all var(--duration) ease;
  position: relative;
  z-index: 2;
  user-select: none;
}

nav label:active {
  transform: scale(0.94);
}

.burger {
  width: 40%;
  display: flex;
  flex-direction: column;
  position: relative;
  gap: var(--gap);
}

.burger span {
  display: block;
  height: 3px;
  border-radius: 2px;
  background: black;
  transition: all var(--duration) ease;
}

ul.menu-list {
  list-style: none;
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  transform: rotate(-90deg);
  transition: all calc(var(--duration) * 1.5) ease;
}

ul.menu-list li {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--item-size);
  height: var(--item-size);
  aspect-ratio: 1;
  transform: translate(-50%, -50%)
  rotate(calc((var(--i) * 1turn / var(--count)) + var(--offset))) translate(0)
  rotate(calc(-1 * var(--i) * 1turn / var(--count)));
  transform-origin: center center;
  opacity: 0;
  transition: inherit;
}

ul.menu-list a {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  background-color: #fff;
  border-radius: 50%;
  box-shadow: var(--shadow);
  text-decoration: none;
  color: inherit;
  outline: none;
  will-change: transform;
  transition: transform var(--duration) ease;
}

ul.menu-list a::before {
  content: "";
  position: absolute;
  inset: -1px;
  padding: 10px;
  background: linear-gradient(
    to right bottom,
    var(--muted),
    transparent,
    var(--muted)
  );
  z-index: -1 ;
  border-radius: 50%;
}

ul.menu-list a:focus-visible {
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.35), var(--shadow);
}

ul.menu-list a:hover {
  transform: scale(1.1);
  filter: brightness(1.03);
}

span.icon {
  width: 100%;
  height: 100%;
  aspect-ratio: 1;
  display: grid;
  place-items: center;
}

span.icon img {
  width: 50%;
  height: 50%;
}

#toggle:checked ~ ul.menu-list li {
  transform: translate(-50%, -50%)
  rotate(calc((var(--i) * 1turn / var(--count)) + var(--offset))) translate(var(--radius))
  rotate(calc(-1 * var(--i) * 1turn / var(--count)));
  opacity: 1;
}

#toggle:checked ~ ul.menu-list {
  transform: rotate(0);
}

#toggle:checked ~ ul.menu-list a {
  transform: rotate(.25turn);
}

#toggle:checked + label .burger span:nth-child(1) {
  transform: translateY(calc(var(--gap) + 100%)) rotate(.125turn);
}

#toggle:checked + label .burger span:nth-child(2) {
  opacity: 0;
}

#toggle:checked + label .burger span:nth-child(3) {
  transform: translateY(calc(-1 * var(--gap) - 100%))  rotate(-.125turn);
}

.visually-hidden {
  position: absolute ;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Reduces motion for users who are sensitive to animation */@media (prefers-reduced-motion: reduce) {
  ul.menu-list li,
  label .burger span {
      transition: none ;
  }
}

3. Customize the radial menu.

  • Adjusting the radius: Change --radius to increase or decrease the menu’s spread. Larger values create more dramatic animations but require more screen space.
  • Modifying item count: Update both --count and the number of <li> elements. The CSS automatically recalculates positioning for any number of items.
  • Animation timing: Adjust --duration for faster or slower transitions. The menu list uses calc(var(--duration) * 1.5) for a slightly delayed effect that feels more natural.
  • Color theming: Customize --bg, --fg, --muted, and --shadow variables to match your brand colors.

FAQs:

Q: How do I handle more than 8-10 menu items?
A: While technically possible, more than eight items creates crowded layouts and usability issues. Consider grouping related items into sub-menus or using alternative navigation patterns for complex menu structures.

Sponsored

Q: Does this work on mobile devices?
A: Yes, the library includes touch-friendly sizing and responsive design patterns. The aspect-ratio property and flexible units ensure proper scaling across different screen sizes. Test thoroughly on your target devices as touch targets may need adjustment.

Q: Can I customize the opening animation direction?
A: Modify the --offset variable to change the starting position. The default -0.25turn starts items from the top. Use 0turn to start from the right, 0.25turn for bottom, or 0.5turn for left positioning.

Q: Can I position the menu in a corner of the screen, like a real FAB?
A: Absolutely. You can wrap the nav.menu element in a container and apply position: fixed or position: absolute to it. Then use properties like bottom: 2rem; and right: 2rem; to place it wherever you need it.

Related Resources:

The post Build Modern Accessible Circular Menus with Pure CSS – Radial Menu appeared first on CSS Script.

rssfeeds-admin

Share
Published by
rssfeeds-admin

Recent Posts

Six more US troops killed in Iran war, in crash of refueling aircraft

Defense Secretary Pete Hegseth speaks at a briefing at the Pentagon on March 13, 2026.…

14 minutes ago

Instagram is getting rid of end-to-end encrypted DMs that ‘very few’ people used

Instagram will no longer support end-to-end encrypted messages starting May 8th. In a statement to…

44 minutes ago

Your Xbox Ally X Is Due for a Crucial Performance Update in April

Microsoft announced at GDC today that it's adding Automatic Super Resolution (Auto SR) to the…

54 minutes ago

The Lenovo Legion 5 Gaming Laptop with OLED Display and RTX 5070 GPU Drops to $1,330

Lenovo's annual Spring Sale is well underway with this great deal on an affordable yet…

54 minutes ago

Amazon Is Offering Free NBA Streaming to Prime Members This Weekend

We’ve reached the final month of this year’s regular NBA season, which has been a…

54 minutes ago

Google Pixel 10A review: Just buy the 9A

I'm not entirely sure why the Pixel 10A exists. Google hasn't upgraded the chipset, cameras,…

2 hours ago

This website uses cookies.