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

  • 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.

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

How AI is Powering the Next Generation of Scam Detection Systems

Frauds are no longer spotted by disorganized phishing emails that contain spelling errors. They are…

43 minutes ago

Microsoft 365 Web Services Hit by Google Chrome 147 Compatibility Issue

Microsoft is actively investigating a widespread authentication issue affecting users attempting to access Microsoft 365…

44 minutes ago

Two U.S. Nationals Sentenced for Running Laptop Farm for DPRK Remote Workers

Two American nationals have been sentenced to federal prison for operating a sophisticated “laptop farm”…

44 minutes ago

New UAC-0247 Campaign Steals Browser and WhatsApp Data From Hospitals and Governments

A threat cluster tracked as UAC-0247 has been running an active campaign since early 2026,…

44 minutes ago

WOWO EXCLUSIVE – Governor Braun talks Endorsements and Iron Nation with Kayla

FORT WAYNE, IND. (WOWO) Indiana Governor Mike Braun is weighing in on multiple contested primary races…

53 minutes ago

Jared Leto’s Skeletor Voice Is Not Inspired by Tom Hardy’s Bane, Masters of the Universe Director Says

The internet definitely had some thoughts about Jared Leto’s Skeletor voice when the first Masters…

54 minutes ago

This website uses cookies.