What You'll Build

In this tutorial you'll create a CSS expandable navigation bar with animated gradient icons — a row of circular pill buttons, each containing an Ionicon. When a user hovers, the pill smoothly expands from 60 px to 180 px, the icon scales away and a text label fades in, while a gradient background floods the pill and a matching glow bleeds underneath it. Every item carries its own two-color gradient defined with a pair of inline CSS custom properties.

This project is ideal for anyone learning CSS transitions, CSS custom properties, pseudo-element layering, and the :hover pseudo-class. The skills transfer directly to toolbars, docks, sidebars, social media link lists, and any UI that needs a compact icon state paired with a readable label.

If you enjoy effects that combine icon animations with glow, also check out the Electric Neon Card Animation for another take on CSS-powered glow techniques.


Key Features of This Navigation Bar

💊
Pill Expand on Hover
Each nav item widens from a compact icon circle to a full labeled pill with a single CSS transition.
🎨
Per-Item Gradient Colors
Two inline CSS variables (--i and --j) give every item its own unique two-stop gradient without duplicating any rules.
Blur Glow Shadow
A ::after pseudo-element with filter: blur(15px) creates a soft colored light bleed below the expanded pill.
🔄
Staggered Icon/Label Swap
The icon and label swap using opposite transition-delay values so they never overlap mid-animation.
🪶
Zero JavaScript
The entire interaction is driven by the CSS :hover pseudo-class — no JS event listeners needed.
GPU-Accelerated
Animates transform, opacity, and width — the browser compositor handles the smooth transitions.

Full Source Code (Free)

The project is a single HTML file with an embedded <style> block and Ionicons loaded from a CDN. Use the tabs below to view the HTML structure and the CSS separately. Copy both and paste them into a single index.html file to run it instantly.

HTML — index.html
CSS — style.css
💡 Quick Start: Paste both blocks into one index.html file and open it in any modern browser. Hover each pill to see the expansion and glow instantly. Or click Preview Result above to see it live right here on the page.

How It Works — Step by Step

Every visual layer of this navigation bar is intentional. Here is a precise breakdown of how each CSS rule contributes to the final effect.

01

Per-Item Gradient via Inline CSS Variables

Each <li> has an inline style attribute defining --i (start color) and --j (end color). The CSS uses these in background: linear-gradient(45deg, var(--i), var(--j)) on the ::before pseudo-element. This way a single CSS rule drives five completely different gradients — purple-pink, sky-blue, orange-red, green-cyan, and rose-magenta.

02

Base Pill Shape and Box Shadow

Each li starts as a 60 × 60 px circle: border-radius: 60px, white background, and a subtle box-shadow. The element uses display: flex; justify-content: center; align-items: center to center the icon. overflow is left at its default so the pseudo-elements can extend outside during the glow effect.

03

Pseudo-element Layering: Gradient Fill and Glow

::before sits above the white background with inset: 0, matching the pill's border-radius, and holds the gradient fill. It starts at opacity: 0 and transitions to 1 on hover. ::after is offset downward (top: 10px), blurred with filter: blur(15px), and set to z-index: -1 so it appears as a glow shadow beneath the pill. It also starts transparent and fades to opacity: 0.5 on hover.

04

Width Expansion on Hover

On li:hover, width transitions from 60 px to 180 px with transition: 0.5s. Because the pill uses flexbox centering, the icon and label naturally stay centered as the element grows. The box-shadow is also removed on hover to let the glow pseudo-element be the only shadow source.

05

Icon Scale-Out and Color Change

The ion-icon is colored #777 at rest. On li:hover ion-icon, it transitions to transform: scale(0) with color: #fff — it disappears into a dot. Crucially, its transition-delay is 0s on hover so it exits immediately, making room for the label before the expansion is halfway done.

06

Label Scale-In with Staggered Delay

The .title span starts at transform: scale(0). On li:hover .title it scales to 1, but with transition-delay: 0.25s. This 250 ms gap ensures the icon has already shrunk before the label appears, keeping the animation uncluttered. On mouse-out, the delays reverse: the label exits first, then the icon returns.

⚠️ Animating width: Unlike transform, animating width does trigger layout recalculation. For a navigation bar with a small number of items this is imperceptible, but avoid using this pattern on dozens of elements animating simultaneously. For high-performance scenarios, consider using transform: scaleX() combined with transform-origin: left instead.

Customization Options

This navigation bar is designed to be remixed. Here are the most common adjustments and exactly where to make them.

Changing a gradient

Every list item's two-stop gradient is controlled entirely by the inline --i and --j variables. To change the purple-pink item to a teal-lime gradient, update its opening tag from style="--i:#a955ff;--j:#ea51ff" to style="--i:#14b8a6;--j:#84cc16". No CSS file changes needed.

Adding a new navigation item

Copy any existing <li> block. Change its --i and --j color variables, update the name attribute on <ion-icon> to any icon from the Ionicons library, and set the .title text. The layout flex gap handles spacing automatically.

Adjusting expand width

The expanded width is hardcoded to 180px in ul li:hover. Increase it for longer labels (e.g. "Settings" needs around 200 px) or decrease it for short labels like "Go". Match the font-size and letter-spacing on .title accordingly.

Swap Ionicons for another icon set

Replace the <ion-icon> elements with any icon system that renders via inline SVG, a <i> tag, or a <span>. The only CSS change required is updating the selector from ul li ion-icon to whatever tag your icon library uses.

Vertical layout

Change ul from display: flex to display: flex; flex-direction: column and remove the fixed width: 60px from li. Set a min-width: 60px instead so the pill can expand horizontally in a sidebar context.


Best Practices & Common Mistakes

✅ Do
  • Keep --i and --j as inline variables per item for zero CSS duplication
  • Use transition-delay to stagger icon and label swaps cleanly
  • Set z-index: -1 on the glow ::after so it never obscures content
  • Add @media (prefers-reduced-motion: reduce) to disable transitions for accessibility
  • Test on a real device to confirm the hover state works as expected
❌ Don't
  • Animate more than 8–10 items simultaneously — layout cost from width grows linearly
  • Forget to set overflow: hidden on a parent if you want glow clipped to a container
  • Hardcode gradient colors in CSS when inline variables already handle per-item theming
  • Rely on hover-only interactions for primary navigation on a touch-first product
  • Use the same --i and --j colors on two adjacent items — the visual distinction is the whole point

Browser Compatibility

Every CSS feature used in this project has been broadly supported for years. The table below covers the key techniques and their minimum browser support.

Feature Chrome Firefox Safari Edge
CSS Custom Properties (var()) ✓ 49+ ✓ 31+ ✓ 9.1+ ✓ 15+
CSS transition ✓ All ✓ All ✓ All ✓ All
::before / ::after pseudo-elements ✓ All ✓ All ✓ All ✓ All
filter: blur() ✓ 18+ ✓ 35+ ✓ 6+ ✓ 12+
linear-gradient ✓ All ✓ All ✓ All ✓ All
ℹ️ Ionicons note: Ionicons renders via a Web Component (ion-icon). Web Components are supported in all evergreen browsers. Internet Explorer 11 is not supported, but IE11's market share is effectively zero in 2026.

Performance & Responsiveness

Animation performance

The most performance-sensitive operation here is the width transition on <li>. Changing width triggers a layout reflow, which is more expensive than a transform. However, with five small items this cost is negligible — browsers batch the reflow for a single frame and the result is visually indistinguishable from a transform-based approach for this number of elements.

The filter: blur() on ::after is GPU-composited in all major browsers and does not cause additional layout work. transform: scale() on the icon and label are also composited, keeping those transitions off the main thread.

Responsiveness

The <ul> uses display: flex with gap: 25px. On narrow viewports the row will overflow if the items are all expanded simultaneously (unlikely in normal use, but worth noting). To make the bar responsive, wrap it in a container with overflow-x: auto and add padding so scrolling is comfortable on small screens. On touch devices, consider replacing the hover behavior with a tap-toggle using a small JavaScript click handler, since :hover does not persist on mobile.


Frequently Asked Questions

Can I build this expandable navigation bar without JavaScript?
Yes — the expansion, gradient reveal, icon swap, and glow effect are all driven by the CSS :hover pseudo-class and transition property. You need only an HTML file; no JavaScript is involved at any step.
How do per-item gradient colors work with CSS custom properties?
Each <li> carries two inline CSS variables — --i (gradient start) and --j (gradient end). The ::before and ::after pseudo-elements reference these with linear-gradient(45deg, var(--i), var(--j)). Because CSS custom properties cascade down into pseudo-elements, changing the inline values on a parent element automatically updates both pseudo-elements with no extra CSS rules.
How do I add a new navigation item?
Copy any existing <li>, set its --i and --j inline colors to your chosen gradient stops, replace the ion-icon name attribute with any icon from the Ionicons library, and update the span.title text. The flexbox gap on <ul> handles the spacing automatically.
Why do the icon and label swap instead of appearing at the same time?
Each element has a different transition-delay. On hover, the icon collapses immediately (delay: 0s) while the label expands after 250 ms (delay: 0.25s). On mouse-out the reverse happens. This staggering ensures that only one element is visible at any moment during the transition, producing a clean single-focus read rather than an overlapping jumble.
Does the blur glow under the pill hurt performance?
No, not for a small bar of five items. The glow is applied with filter: blur() on a ::after pseudo-element, which browsers composite on the GPU separately from the main document layout. For large numbers of items animating simultaneously, consider replacing the blur glow with a simpler box-shadow transition instead.
Can I use this navigation bar on mobile devices?
The bar renders correctly on all screen sizes and the icons are touch-friendly at 60 × 60 px. However, the hover-expand animation does not persist on touch screens — a tap activates the hover state briefly but it doesn't stay open. For a mobile-first deployment, add a small JavaScript click toggle, or display the labels permanently alongside the icons on narrow viewports using a media query.

Conclusion

This CSS expandable navigation bar demonstrates how a single CSS mechanic — the :hover pseudo-class combined with transition — can produce a layered, polished interaction with zero JavaScript. By stacking gradient pseudo-elements, staggering transition delays, and routing per-item colors through inline CSS variables, five visually distinct items share a single CSS rule set.

The pattern scales naturally: swap the icons, change two hex values per item, adjust the expanded width, and you have a completely different navigation bar. Use it as a toolbar at the bottom of a dashboard, a social links row in a portfolio footer, or a quick-access dock for a web app. The underlying CSS technique — pill expand, pseudo-element gradient reveal, blur glow, staggered label swap — is reusable across dozens of UI components.

Found this tutorial useful? Browse the related projects in the sidebar for more modern CSS interaction effects and animation techniques.