15 CSS Navigation Menu Designs 08 / 15
CSS Tab Navigation with Animated Indicator
A tab navigation component with a smooth sliding pill indicator that moves between tabs using only CSS.
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<div class="nav-08">
<!-- Pill tabs -->
<div class="nav-08__demo-wrap">
<p class="nav-08__demo-label">Pill / Capsule Tabs</p>
<input type="radio" name="nav-08-t1" id="nav-08-t1-1" checked>
<input type="radio" name="nav-08-t1" id="nav-08-t1-2">
<input type="radio" name="nav-08-t1" id="nav-08-t1-3">
<input type="radio" name="nav-08-t1" id="nav-08-t1-4">
<div class="nav-08__tabs-1">
<label for="nav-08-t1-1">Overview</label>
<label for="nav-08-t1-2">Analytics</label>
<label for="nav-08-t1-3">Reports</label>
<label for="nav-08-t1-4">Settings</label>
<div class="nav-08__pill"></div>
</div>
<div class="nav-08__panels-1">
<div class="nav-08__panel"><h3>Overview</h3><p>A summary of all key metrics across your workspace, updated in real time.</p></div>
<div class="nav-08__panel"><h3>Analytics</h3><p>Deep-dive charts showing traffic, conversions, and user behaviour trends.</p></div>
<div class="nav-08__panel"><h3>Reports</h3><p>Scheduled reports delivered to your inbox every Monday morning.</p></div>
<div class="nav-08__panel"><h3>Settings</h3><p>Configure notifications, integrations, and workspace preferences here.</p></div>
</div>
</div>
<!-- Underline tabs -->
<div class="nav-08__demo-wrap">
<p class="nav-08__demo-label">Underline Tabs</p>
<input type="radio" name="nav-08-t2" id="nav-08-t2-1" checked>
<input type="radio" name="nav-08-t2" id="nav-08-t2-2">
<input type="radio" name="nav-08-t2" id="nav-08-t2-3">
<div class="nav-08__tabs-2">
<label for="nav-08-t2-1">Activity</label>
<label for="nav-08-t2-2">Integrations</label>
<label for="nav-08-t2-3">Members</label>
<div class="nav-08__underline"></div>
</div>
<div class="nav-08__panels-2">
<div class="nav-08__panel"><h3>Activity</h3><p>Latest actions taken by team members in this workspace over the past 7 days.</p></div>
<div class="nav-08__panel"><h3>Integrations</h3><p>Connect Slack, GitHub, Jira, and 40+ other tools with one click.</p></div>
<div class="nav-08__panel"><h3>Members</h3><p>Manage roles, permissions, and invitations for every workspace member.</p></div>
</div>
</div>
<!-- Segmented control -->
<div class="nav-08__demo-wrap">
<p class="nav-08__demo-label">Segmented Control</p>
<input type="radio" name="nav-08-s" id="nav-08-s1" checked>
<input type="radio" name="nav-08-s" id="nav-08-s2">
<input type="radio" name="nav-08-s" id="nav-08-s3">
<div class="nav-08__seg">
<label for="nav-08-s1">Daily</label>
<label for="nav-08-s2">Weekly</label>
<label for="nav-08-s3">Monthly</label>
<div class="nav-08__seg-thumb"></div>
</div>
</div>
</div> <div class="nav-08">
<!-- Pill tabs -->
<div class="nav-08__demo-wrap">
<p class="nav-08__demo-label">Pill / Capsule Tabs</p>
<input type="radio" name="nav-08-t1" id="nav-08-t1-1" checked>
<input type="radio" name="nav-08-t1" id="nav-08-t1-2">
<input type="radio" name="nav-08-t1" id="nav-08-t1-3">
<input type="radio" name="nav-08-t1" id="nav-08-t1-4">
<div class="nav-08__tabs-1">
<label for="nav-08-t1-1">Overview</label>
<label for="nav-08-t1-2">Analytics</label>
<label for="nav-08-t1-3">Reports</label>
<label for="nav-08-t1-4">Settings</label>
<div class="nav-08__pill"></div>
</div>
<div class="nav-08__panels-1">
<div class="nav-08__panel"><h3>Overview</h3><p>A summary of all key metrics across your workspace, updated in real time.</p></div>
<div class="nav-08__panel"><h3>Analytics</h3><p>Deep-dive charts showing traffic, conversions, and user behaviour trends.</p></div>
<div class="nav-08__panel"><h3>Reports</h3><p>Scheduled reports delivered to your inbox every Monday morning.</p></div>
<div class="nav-08__panel"><h3>Settings</h3><p>Configure notifications, integrations, and workspace preferences here.</p></div>
</div>
</div>
<!-- Underline tabs -->
<div class="nav-08__demo-wrap">
<p class="nav-08__demo-label">Underline Tabs</p>
<input type="radio" name="nav-08-t2" id="nav-08-t2-1" checked>
<input type="radio" name="nav-08-t2" id="nav-08-t2-2">
<input type="radio" name="nav-08-t2" id="nav-08-t2-3">
<div class="nav-08__tabs-2">
<label for="nav-08-t2-1">Activity</label>
<label for="nav-08-t2-2">Integrations</label>
<label for="nav-08-t2-3">Members</label>
<div class="nav-08__underline"></div>
</div>
<div class="nav-08__panels-2">
<div class="nav-08__panel"><h3>Activity</h3><p>Latest actions taken by team members in this workspace over the past 7 days.</p></div>
<div class="nav-08__panel"><h3>Integrations</h3><p>Connect Slack, GitHub, Jira, and 40+ other tools with one click.</p></div>
<div class="nav-08__panel"><h3>Members</h3><p>Manage roles, permissions, and invitations for every workspace member.</p></div>
</div>
</div>
<!-- Segmented control -->
<div class="nav-08__demo-wrap">
<p class="nav-08__demo-label">Segmented Control</p>
<input type="radio" name="nav-08-s" id="nav-08-s1" checked>
<input type="radio" name="nav-08-s" id="nav-08-s2">
<input type="radio" name="nav-08-s" id="nav-08-s3">
<div class="nav-08__seg">
<label for="nav-08-s1">Daily</label>
<label for="nav-08-s2">Weekly</label>
<label for="nav-08-s3">Monthly</label>
<div class="nav-08__seg-thumb"></div>
</div>
</div>
</div>.nav-08,.nav-08 *,.nav-08 *::before,.nav-08 *::after{box-sizing:border-box;margin:0;padding:0}
.nav-08 ::selection{background:#e11d48;color:#fff}
/* Hide the raw radio inputs. The labels are the visible tab triggers; the
:checked state on the inputs drives the sliding indicator via sibling
selectors. position:absolute + opacity:0 + 1px box keeps them keyboard-
focusable (screen-reader and tab-key users can still activate them) but
visually invisible. display:none would break keyboard accessibility. */
.nav-08 input[type="radio"]{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px;margin:0;}
.nav-08{
--bg:#fff1f2;--surface:#fff;--border:#fecdd3;
--text:#1a0a0d;--muted:#9f1239;
--accent:#e11d48;--accent2:#fb7185;
font-family:'Manrope',system-ui,sans-serif;
background:var(--bg);min-height:100vh;
display:flex;flex-direction:column;align-items:center;
padding:60px 24px;
}
.nav-08__demo-wrap{width:100%;max-width:720px}
.nav-08__demo-wrap + .nav-08__demo-wrap{margin-top:56px}
.nav-08__demo-label{
font-size:.75rem;font-weight:700;color:var(--muted);
letter-spacing:.08em;text-transform:uppercase;margin-bottom:14px;
}
/* === Tab style 1: pill/capsule tabs === */
.nav-08__tabs-1{
background:var(--surface);border:1px solid var(--border);
border-radius:16px;padding:6px;
/* CSS Grid with 4 equal columns so each tab gets exactly 1/4 of
the container width regardless of label text length. The pill
spans one column and shifts by exact multiples — no hardcoded
pixel widths to drift from rendered label sizes. */
display:grid;grid-template-columns:repeat(4,1fr);gap:2px;
position:relative;
}
.nav-08__tabs-1 label{
position:relative;z-index:1;
padding:9px 12px;border-radius:11px;
font-size:.875rem;font-weight:600;color:var(--muted);
cursor:pointer;transition:color .2s;white-space:nowrap;
user-select:none;text-align:center;
}
.nav-08__pill{
position:absolute;top:6px;bottom:6px;
border-radius:11px;background:var(--accent);
/* Pill width = one grid column. With 4 columns + 3 gaps of 2px
inside a container with 6px left+right padding: track width
= 100% - 12px - 6px = 100% - 18px, single column = that / 4. */
width:calc((100% - 18px) / 4);
left:6px;
transition:transform .28s cubic-bezier(.34,1.2,.64,1);
z-index:0;
}
/* The radio inputs sit OUTSIDE .nav-08__tabs-1 (siblings of the
tabs container, not of the .nav-08__pill inside it), so the
old plain-sibling :checked ~ .nav-08__pill selector matched nothing.
:has() lets us style descendants based on a sibling's state. */
.nav-08__demo-wrap:has(#nav-08-t1-1:checked) .nav-08__pill{transform:translateX(0)}
.nav-08__demo-wrap:has(#nav-08-t1-1:checked) label[for="nav-08-t1-1"]{color:#fff}
.nav-08__demo-wrap:has(#nav-08-t1-2:checked) .nav-08__pill{transform:translateX(calc(100% + 2px))}
.nav-08__demo-wrap:has(#nav-08-t1-2:checked) label[for="nav-08-t1-2"]{color:#fff}
.nav-08__demo-wrap:has(#nav-08-t1-3:checked) .nav-08__pill{transform:translateX(calc(200% + 4px))}
.nav-08__demo-wrap:has(#nav-08-t1-3:checked) label[for="nav-08-t1-3"]{color:#fff}
.nav-08__demo-wrap:has(#nav-08-t1-4:checked) .nav-08__pill{transform:translateX(calc(300% + 6px))}
.nav-08__demo-wrap:has(#nav-08-t1-4:checked) label[for="nav-08-t1-4"]{color:#fff}
/* panel content */
.nav-08__panels-1{
background:var(--surface);border:1px solid var(--border);
border-radius:16px;margin-top:4px;overflow:hidden;
}
/* show panel matching checked */
.nav-08__panel{display:none;padding:28px}
#nav-08-t1-1:checked ~ .nav-08__panels-1 .nav-08__panel:nth-child(1){display:block}
#nav-08-t1-2:checked ~ .nav-08__panels-1 .nav-08__panel:nth-child(2){display:block}
#nav-08-t1-3:checked ~ .nav-08__panels-1 .nav-08__panel:nth-child(3){display:block}
#nav-08-t1-4:checked ~ .nav-08__panels-1 .nav-08__panel:nth-child(4){display:block}
.nav-08__panel h3{font-size:1rem;font-weight:700;color:var(--text);margin-bottom:8px}
.nav-08__panel p{font-size:.9rem;color:var(--muted);line-height:1.6}
/* === Tab style 2: underline tabs === */
.nav-08__tabs-2{
/* 3-column grid for 3 equally-sized tabs. Underline width matches
a single column exactly. */
display:grid;grid-template-columns:repeat(3,1fr);
border-bottom:2px solid var(--border);
position:relative;
}
.nav-08__tabs-2 label{
padding:12px 20px;font-size:.875rem;font-weight:600;
color:var(--muted);cursor:pointer;transition:color .2s;
user-select:none;text-align:center;
}
.nav-08__tabs-2 label:hover{color:var(--text)}
.nav-08__underline{
position:absolute;bottom:-2px;height:2px;
background:var(--accent);border-radius:2px;
width:calc(100% / 3);left:0;
transition:transform .28s cubic-bezier(.4,0,.2,1);
}
/* Same :has() pattern — radios are outside .nav-08__tabs-2. */
.nav-08__demo-wrap:has(#nav-08-t2-1:checked) .nav-08__underline{transform:translateX(0)}
.nav-08__demo-wrap:has(#nav-08-t2-1:checked) label[for="nav-08-t2-1"]{color:var(--text)}
.nav-08__demo-wrap:has(#nav-08-t2-2:checked) .nav-08__underline{transform:translateX(100%)}
.nav-08__demo-wrap:has(#nav-08-t2-2:checked) label[for="nav-08-t2-2"]{color:var(--text)}
.nav-08__demo-wrap:has(#nav-08-t2-3:checked) .nav-08__underline{transform:translateX(200%)}
.nav-08__demo-wrap:has(#nav-08-t2-3:checked) label[for="nav-08-t2-3"]{color:var(--text)}
.nav-08__panels-2{
background:var(--surface);border:1px solid var(--border);
border-top:none;border-radius:0 0 14px 14px;
}
#nav-08-t2-1:checked ~ .nav-08__panels-2 .nav-08__panel:nth-child(1){display:block}
#nav-08-t2-2:checked ~ .nav-08__panels-2 .nav-08__panel:nth-child(2){display:block}
#nav-08-t2-3:checked ~ .nav-08__panels-2 .nav-08__panel:nth-child(3){display:block}
/* === Segmented control === */
.nav-08__seg{
/* 3-column grid so each segment label is the same width as the
thumb that highlights it. */
display:grid;grid-template-columns:repeat(3,1fr);gap:2px;
background:var(--border);border-radius:10px;padding:3px;position:relative;
}
.nav-08__seg label{
padding:8px 18px;border-radius:8px;font-size:.8125rem;font-weight:600;
color:var(--muted);cursor:pointer;transition:color .2s;
position:relative;z-index:1;user-select:none;text-align:center;
}
.nav-08__seg-thumb{
position:absolute;top:3px;bottom:3px;
background:#fff;border-radius:8px;
box-shadow:0 1px 4px rgba(0,0,0,.1);
/* width = one column = (track width minus 6px padding and 4px gaps) / 3 */
width:calc((100% - 10px) / 3);
left:3px;
transition:transform .25s cubic-bezier(.34,1.2,.64,1);
z-index:0;
}
/* :has() — same pattern as the other two tab styles. */
.nav-08__demo-wrap:has(#nav-08-s1:checked) .nav-08__seg-thumb{transform:translateX(0)}
.nav-08__demo-wrap:has(#nav-08-s1:checked) label[for="nav-08-s1"]{color:var(--text)}
.nav-08__demo-wrap:has(#nav-08-s2:checked) .nav-08__seg-thumb{transform:translateX(calc(100% + 2px))}
.nav-08__demo-wrap:has(#nav-08-s2:checked) label[for="nav-08-s2"]{color:var(--text)}
.nav-08__demo-wrap:has(#nav-08-s3:checked) .nav-08__seg-thumb{transform:translateX(calc(200% + 4px))}
.nav-08__demo-wrap:has(#nav-08-s3:checked) label[for="nav-08-s3"]{color:var(--text)}
@media(prefers-reduced-motion:reduce){
.nav-08__pill,.nav-08__underline,.nav-08__seg-thumb{transition:none}
} .nav-08,.nav-08 *,.nav-08 *::before,.nav-08 *::after{box-sizing:border-box;margin:0;padding:0}
.nav-08 ::selection{background:#e11d48;color:#fff}
/* Hide the raw radio inputs. The labels are the visible tab triggers; the
:checked state on the inputs drives the sliding indicator via sibling
selectors. position:absolute + opacity:0 + 1px box keeps them keyboard-
focusable (screen-reader and tab-key users can still activate them) but
visually invisible. display:none would break keyboard accessibility. */
.nav-08 input[type="radio"]{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px;margin:0;}
.nav-08{
--bg:#fff1f2;--surface:#fff;--border:#fecdd3;
--text:#1a0a0d;--muted:#9f1239;
--accent:#e11d48;--accent2:#fb7185;
font-family:'Manrope',system-ui,sans-serif;
background:var(--bg);min-height:100vh;
display:flex;flex-direction:column;align-items:center;
padding:60px 24px;
}
.nav-08__demo-wrap{width:100%;max-width:720px}
.nav-08__demo-wrap + .nav-08__demo-wrap{margin-top:56px}
.nav-08__demo-label{
font-size:.75rem;font-weight:700;color:var(--muted);
letter-spacing:.08em;text-transform:uppercase;margin-bottom:14px;
}
/* === Tab style 1: pill/capsule tabs === */
.nav-08__tabs-1{
background:var(--surface);border:1px solid var(--border);
border-radius:16px;padding:6px;
/* CSS Grid with 4 equal columns so each tab gets exactly 1/4 of
the container width regardless of label text length. The pill
spans one column and shifts by exact multiples — no hardcoded
pixel widths to drift from rendered label sizes. */
display:grid;grid-template-columns:repeat(4,1fr);gap:2px;
position:relative;
}
.nav-08__tabs-1 label{
position:relative;z-index:1;
padding:9px 12px;border-radius:11px;
font-size:.875rem;font-weight:600;color:var(--muted);
cursor:pointer;transition:color .2s;white-space:nowrap;
user-select:none;text-align:center;
}
.nav-08__pill{
position:absolute;top:6px;bottom:6px;
border-radius:11px;background:var(--accent);
/* Pill width = one grid column. With 4 columns + 3 gaps of 2px
inside a container with 6px left+right padding: track width
= 100% - 12px - 6px = 100% - 18px, single column = that / 4. */
width:calc((100% - 18px) / 4);
left:6px;
transition:transform .28s cubic-bezier(.34,1.2,.64,1);
z-index:0;
}
/* The radio inputs sit OUTSIDE .nav-08__tabs-1 (siblings of the
tabs container, not of the .nav-08__pill inside it), so the
old plain-sibling :checked ~ .nav-08__pill selector matched nothing.
:has() lets us style descendants based on a sibling's state. */
.nav-08__demo-wrap:has(#nav-08-t1-1:checked) .nav-08__pill{transform:translateX(0)}
.nav-08__demo-wrap:has(#nav-08-t1-1:checked) label[for="nav-08-t1-1"]{color:#fff}
.nav-08__demo-wrap:has(#nav-08-t1-2:checked) .nav-08__pill{transform:translateX(calc(100% + 2px))}
.nav-08__demo-wrap:has(#nav-08-t1-2:checked) label[for="nav-08-t1-2"]{color:#fff}
.nav-08__demo-wrap:has(#nav-08-t1-3:checked) .nav-08__pill{transform:translateX(calc(200% + 4px))}
.nav-08__demo-wrap:has(#nav-08-t1-3:checked) label[for="nav-08-t1-3"]{color:#fff}
.nav-08__demo-wrap:has(#nav-08-t1-4:checked) .nav-08__pill{transform:translateX(calc(300% + 6px))}
.nav-08__demo-wrap:has(#nav-08-t1-4:checked) label[for="nav-08-t1-4"]{color:#fff}
/* panel content */
.nav-08__panels-1{
background:var(--surface);border:1px solid var(--border);
border-radius:16px;margin-top:4px;overflow:hidden;
}
/* show panel matching checked */
.nav-08__panel{display:none;padding:28px}
#nav-08-t1-1:checked ~ .nav-08__panels-1 .nav-08__panel:nth-child(1){display:block}
#nav-08-t1-2:checked ~ .nav-08__panels-1 .nav-08__panel:nth-child(2){display:block}
#nav-08-t1-3:checked ~ .nav-08__panels-1 .nav-08__panel:nth-child(3){display:block}
#nav-08-t1-4:checked ~ .nav-08__panels-1 .nav-08__panel:nth-child(4){display:block}
.nav-08__panel h3{font-size:1rem;font-weight:700;color:var(--text);margin-bottom:8px}
.nav-08__panel p{font-size:.9rem;color:var(--muted);line-height:1.6}
/* === Tab style 2: underline tabs === */
.nav-08__tabs-2{
/* 3-column grid for 3 equally-sized tabs. Underline width matches
a single column exactly. */
display:grid;grid-template-columns:repeat(3,1fr);
border-bottom:2px solid var(--border);
position:relative;
}
.nav-08__tabs-2 label{
padding:12px 20px;font-size:.875rem;font-weight:600;
color:var(--muted);cursor:pointer;transition:color .2s;
user-select:none;text-align:center;
}
.nav-08__tabs-2 label:hover{color:var(--text)}
.nav-08__underline{
position:absolute;bottom:-2px;height:2px;
background:var(--accent);border-radius:2px;
width:calc(100% / 3);left:0;
transition:transform .28s cubic-bezier(.4,0,.2,1);
}
/* Same :has() pattern — radios are outside .nav-08__tabs-2. */
.nav-08__demo-wrap:has(#nav-08-t2-1:checked) .nav-08__underline{transform:translateX(0)}
.nav-08__demo-wrap:has(#nav-08-t2-1:checked) label[for="nav-08-t2-1"]{color:var(--text)}
.nav-08__demo-wrap:has(#nav-08-t2-2:checked) .nav-08__underline{transform:translateX(100%)}
.nav-08__demo-wrap:has(#nav-08-t2-2:checked) label[for="nav-08-t2-2"]{color:var(--text)}
.nav-08__demo-wrap:has(#nav-08-t2-3:checked) .nav-08__underline{transform:translateX(200%)}
.nav-08__demo-wrap:has(#nav-08-t2-3:checked) label[for="nav-08-t2-3"]{color:var(--text)}
.nav-08__panels-2{
background:var(--surface);border:1px solid var(--border);
border-top:none;border-radius:0 0 14px 14px;
}
#nav-08-t2-1:checked ~ .nav-08__panels-2 .nav-08__panel:nth-child(1){display:block}
#nav-08-t2-2:checked ~ .nav-08__panels-2 .nav-08__panel:nth-child(2){display:block}
#nav-08-t2-3:checked ~ .nav-08__panels-2 .nav-08__panel:nth-child(3){display:block}
/* === Segmented control === */
.nav-08__seg{
/* 3-column grid so each segment label is the same width as the
thumb that highlights it. */
display:grid;grid-template-columns:repeat(3,1fr);gap:2px;
background:var(--border);border-radius:10px;padding:3px;position:relative;
}
.nav-08__seg label{
padding:8px 18px;border-radius:8px;font-size:.8125rem;font-weight:600;
color:var(--muted);cursor:pointer;transition:color .2s;
position:relative;z-index:1;user-select:none;text-align:center;
}
.nav-08__seg-thumb{
position:absolute;top:3px;bottom:3px;
background:#fff;border-radius:8px;
box-shadow:0 1px 4px rgba(0,0,0,.1);
/* width = one column = (track width minus 6px padding and 4px gaps) / 3 */
width:calc((100% - 10px) / 3);
left:3px;
transition:transform .25s cubic-bezier(.34,1.2,.64,1);
z-index:0;
}
/* :has() — same pattern as the other two tab styles. */
.nav-08__demo-wrap:has(#nav-08-s1:checked) .nav-08__seg-thumb{transform:translateX(0)}
.nav-08__demo-wrap:has(#nav-08-s1:checked) label[for="nav-08-s1"]{color:var(--text)}
.nav-08__demo-wrap:has(#nav-08-s2:checked) .nav-08__seg-thumb{transform:translateX(calc(100% + 2px))}
.nav-08__demo-wrap:has(#nav-08-s2:checked) label[for="nav-08-s2"]{color:var(--text)}
.nav-08__demo-wrap:has(#nav-08-s3:checked) .nav-08__seg-thumb{transform:translateX(calc(200% + 4px))}
.nav-08__demo-wrap:has(#nav-08-s3:checked) label[for="nav-08-s3"]{color:var(--text)}
@media(prefers-reduced-motion:reduce){
.nav-08__pill,.nav-08__underline,.nav-08__seg-thumb{transition:none}
}How this works
Each tab is a `
Customize
- Change `--indicator-color` to customize the active pill. Modify `border-radius` on the `::before` element for square vs pill shape. Add a `box-shadow` to the active indicator for a floating effect.
Watch out for
- CSS custom properties used for layout (like `left` and `width`) must be explicitly transitioned with `transition: left 0.3s, width 0.3s`. The `transition: all` shorthand does not animate custom properties — only their consuming `calc()` values.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| all modern | all modern | all modern | all modern |
More from 15 CSS Navigation Menu Designs
CSS Navigation with Glassmorphism EffectCSS Animated Navigation Icons with LabelsCSS Multi-Level Accordion NavigationCSS Neon Glow Navigation MenuCSS Morphing Navigation Pill IndicatorCSS Sticky Navigation Bar with Scroll ShrinkCSS Navigation with Magnetic Hover EffectCSS Horizontal Navigation Bar with Hover UnderlineCSS Dropdown Navigation MenuCSS Mega Menu NavigationCSS Hamburger Menu with Slide-Out DrawerCSS Full-Screen Overlay Navigation
View the full collection →