24 CSS Liquid Glass Navbars17 / 24
Sliding Capsule Active Tab
A segmented glass tab bar where a frosted capsule physically slides behind the selected item — no JavaScript. Native radio inputs hold the state and modern CSS :has() reads which is checked to translate the capsule, so the active highlight glides between tabs on a spring.
Published
The code
<section class="nb-17" aria-label="Sliding capsule active tab demo">
<div class="nb-17__bar" role="tablist" aria-label="Views">
<span class="nb-17__cap" aria-hidden="true"></span>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-1" checked>
<label class="nb-17__tab" for="nb-17-1">Day</label>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-2">
<label class="nb-17__tab" for="nb-17-2">Week</label>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-3">
<label class="nb-17__tab" for="nb-17-3">Month</label>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-4">
<label class="nb-17__tab" for="nb-17-4">Year</label>
</div>
</section><section class="nb-17" aria-label="Sliding capsule active tab demo">
<div class="nb-17__bar" role="tablist" aria-label="Views">
<span class="nb-17__cap" aria-hidden="true"></span>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-1" checked>
<label class="nb-17__tab" for="nb-17-1">Day</label>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-2">
<label class="nb-17__tab" for="nb-17-2">Week</label>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-3">
<label class="nb-17__tab" for="nb-17-3">Month</label>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-4">
<label class="nb-17__tab" for="nb-17-4">Year</label>
</div>
</section>.nb-17,
.nb-17 *,
.nb-17 *::before,
.nb-17 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.nb-17 {
display: grid;
place-items: center;
width: 100%;
min-height: 100vh;
padding: 20px;
font-family: 'Segoe UI',system-ui,sans-serif;
background: conic-gradient(from 200deg at 50% 50%,#00c2ff,#8e2de2,#ff6a88,#00c2ff);
background-size: 200% 200%;
animation: nb-17-bg 20s ease-in-out infinite;
}
@keyframes nb-17-bg {
50% {
background-position: 100% 100%;
}
}
.nb-17__bar {
position: relative;
display: flex;
padding: 7px;
border-radius: 9999px;
isolation: isolate;
background: color-mix(in oklab,white 14%,transparent);
border: 1px solid rgba(255,255,255,.4);
box-shadow: inset 0 1px 0 rgba(255,255,255,.55),0 20px 44px -24px rgba(0,0,0,.55);
backdrop-filter: blur(22px) saturate(180%);
-webkit-backdrop-filter: blur(22px) saturate(180%);
}
.nb-17__cap {
position: absolute;
top: 7px;
bottom: 7px;
left: 7px;
width: 86px;
border-radius: 9999px;
background: rgba(255,255,255,.26);
border: 1px solid rgba(255,255,255,.55);
box-shadow: inset 0 1px 0 rgba(255,255,255,.7);
z-index: 0;
transition: transform .45s cubic-bezier(.34,1.56,.5,1);
}
.nb-17__r {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
.nb-17__tab {
position: relative;
z-index: 1;
width: 86px;
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 9999px;
cursor: pointer;
color: rgba(255,255,255,.85);
font-size: 14.5px;
font-weight: 600;
transition: color .25s;
}
.nb-17__bar:has(#nb-17-2:checked) .nb-17__cap {
transform: translateX(86px);
}
.nb-17__bar:has(#nb-17-3:checked) .nb-17__cap {
transform: translateX(172px);
}
.nb-17__bar:has(#nb-17-4:checked) .nb-17__cap {
transform: translateX(258px);
}
#nb-17-1:checked~label[for="nb-17-1"],
#nb-17-2:checked~label[for="nb-17-2"],
#nb-17-3:checked~label[for="nb-17-3"],
#nb-17-4:checked~label[for="nb-17-4"] {
color: #fff;
}
.nb-17__r:focus-visible+.nb-17__tab {
outline: 2px solid #fff;
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.nb-17,
.nb-17__cap {
animation: none;
transition: none;
}
}.nb-17,
.nb-17 *,
.nb-17 *::before,
.nb-17 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.nb-17 {
display: grid;
place-items: center;
width: 100%;
min-height: 100vh;
padding: 20px;
font-family: 'Segoe UI',system-ui,sans-serif;
background: conic-gradient(from 200deg at 50% 50%,#00c2ff,#8e2de2,#ff6a88,#00c2ff);
background-size: 200% 200%;
animation: nb-17-bg 20s ease-in-out infinite;
}
@keyframes nb-17-bg {
50% {
background-position: 100% 100%;
}
}
.nb-17__bar {
position: relative;
display: flex;
padding: 7px;
border-radius: 9999px;
isolation: isolate;
background: color-mix(in oklab,white 14%,transparent);
border: 1px solid rgba(255,255,255,.4);
box-shadow: inset 0 1px 0 rgba(255,255,255,.55),0 20px 44px -24px rgba(0,0,0,.55);
backdrop-filter: blur(22px) saturate(180%);
-webkit-backdrop-filter: blur(22px) saturate(180%);
}
.nb-17__cap {
position: absolute;
top: 7px;
bottom: 7px;
left: 7px;
width: 86px;
border-radius: 9999px;
background: rgba(255,255,255,.26);
border: 1px solid rgba(255,255,255,.55);
box-shadow: inset 0 1px 0 rgba(255,255,255,.7);
z-index: 0;
transition: transform .45s cubic-bezier(.34,1.56,.5,1);
}
.nb-17__r {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
.nb-17__tab {
position: relative;
z-index: 1;
width: 86px;
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 9999px;
cursor: pointer;
color: rgba(255,255,255,.85);
font-size: 14.5px;
font-weight: 600;
transition: color .25s;
}
.nb-17__bar:has(#nb-17-2:checked) .nb-17__cap {
transform: translateX(86px);
}
.nb-17__bar:has(#nb-17-3:checked) .nb-17__cap {
transform: translateX(172px);
}
.nb-17__bar:has(#nb-17-4:checked) .nb-17__cap {
transform: translateX(258px);
}
#nb-17-1:checked~label[for="nb-17-1"],
#nb-17-2:checked~label[for="nb-17-2"],
#nb-17-3:checked~label[for="nb-17-3"],
#nb-17-4:checked~label[for="nb-17-4"] {
color: #fff;
}
.nb-17__r:focus-visible+.nb-17__tab {
outline: 2px solid #fff;
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.nb-17,
.nb-17__cap {
animation: none;
transition: none;
}
}/* No JavaScript — hidden radio inputs hold the selected tab and CSS :has(#tab:checked) translates the frosted capsule behind it. Arrow keys move between tabs natively. */
/* No JavaScript — hidden radio inputs hold the selected tab and CSS :has(#tab:checked) translates the frosted capsule behind it. Arrow keys move between tabs natively. */Here's a working CSS Liquid Glass Navbar from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Sliding Capsule Active Tab
Source: https://codefronts.com/navigation/css-liquid-glass-navbars/sliding-capsule-active-tab/
A segmented glass tab bar where a frosted capsule physically slides behind the selected item — no JavaScript. Native radio inputs hold the state and modern CSS :has() reads which is checked to translate the capsule, so the active highlight glides between tabs on a spring.
## HTML
```html
<section class="nb-17" aria-label="Sliding capsule active tab demo">
<div class="nb-17__bar" role="tablist" aria-label="Views">
<span class="nb-17__cap" aria-hidden="true"></span>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-1" checked>
<label class="nb-17__tab" for="nb-17-1">Day</label>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-2">
<label class="nb-17__tab" for="nb-17-2">Week</label>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-3">
<label class="nb-17__tab" for="nb-17-3">Month</label>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-4">
<label class="nb-17__tab" for="nb-17-4">Year</label>
</div>
</section>
```
## CSS
```css
.nb-17,
.nb-17 *,
.nb-17 *::before,
.nb-17 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.nb-17 {
display: grid;
place-items: center;
width: 100%;
min-height: 100vh;
padding: 20px;
font-family: 'Segoe UI',system-ui,sans-serif;
background: conic-gradient(from 200deg at 50% 50%,#00c2ff,#8e2de2,#ff6a88,#00c2ff);
background-size: 200% 200%;
animation: nb-17-bg 20s ease-in-out infinite;
}
@keyframes nb-17-bg {
50% {
background-position: 100% 100%;
}
}
.nb-17__bar {
position: relative;
display: flex;
padding: 7px;
border-radius: 9999px;
isolation: isolate;
background: color-mix(in oklab,white 14%,transparent);
border: 1px solid rgba(255,255,255,.4);
box-shadow: inset 0 1px 0 rgba(255,255,255,.55),0 20px 44px -24px rgba(0,0,0,.55);
backdrop-filter: blur(22px) saturate(180%);
-webkit-backdrop-filter: blur(22px) saturate(180%);
}
.nb-17__cap {
position: absolute;
top: 7px;
bottom: 7px;
left: 7px;
width: 86px;
border-radius: 9999px;
background: rgba(255,255,255,.26);
border: 1px solid rgba(255,255,255,.55);
box-shadow: inset 0 1px 0 rgba(255,255,255,.7);
z-index: 0;
transition: transform .45s cubic-bezier(.34,1.56,.5,1);
}
.nb-17__r {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
.nb-17__tab {
position: relative;
z-index: 1;
width: 86px;
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 9999px;
cursor: pointer;
color: rgba(255,255,255,.85);
font-size: 14.5px;
font-weight: 600;
transition: color .25s;
}
.nb-17__bar:has(#nb-17-2:checked) .nb-17__cap {
transform: translateX(86px);
}
.nb-17__bar:has(#nb-17-3:checked) .nb-17__cap {
transform: translateX(172px);
}
.nb-17__bar:has(#nb-17-4:checked) .nb-17__cap {
transform: translateX(258px);
}
#nb-17-1:checked~label[for="nb-17-1"],
#nb-17-2:checked~label[for="nb-17-2"],
#nb-17-3:checked~label[for="nb-17-3"],
#nb-17-4:checked~label[for="nb-17-4"] {
color: #fff;
}
.nb-17__r:focus-visible+.nb-17__tab {
outline: 2px solid #fff;
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.nb-17,
.nb-17__cap {
animation: none;
transition: none;
}
}
```
## JavaScript
```js
/* No JavaScript — hidden radio inputs hold the selected tab and CSS :has(#tab:checked) translates the frosted capsule behind it. Arrow keys move between tabs natively. */
```Here's a working CSS Liquid Glass Navbar from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Sliding Capsule Active Tab
Source: https://codefronts.com/navigation/css-liquid-glass-navbars/sliding-capsule-active-tab/
A segmented glass tab bar where a frosted capsule physically slides behind the selected item — no JavaScript. Native radio inputs hold the state and modern CSS :has() reads which is checked to translate the capsule, so the active highlight glides between tabs on a spring.
## HTML
```html
<section class="nb-17" aria-label="Sliding capsule active tab demo">
<div class="nb-17__bar" role="tablist" aria-label="Views">
<span class="nb-17__cap" aria-hidden="true"></span>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-1" checked>
<label class="nb-17__tab" for="nb-17-1">Day</label>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-2">
<label class="nb-17__tab" for="nb-17-2">Week</label>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-3">
<label class="nb-17__tab" for="nb-17-3">Month</label>
<input class="nb-17__r" type="radio" name="nb-17" id="nb-17-4">
<label class="nb-17__tab" for="nb-17-4">Year</label>
</div>
</section>
```
## CSS
```css
.nb-17,
.nb-17 *,
.nb-17 *::before,
.nb-17 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.nb-17 {
display: grid;
place-items: center;
width: 100%;
min-height: 100vh;
padding: 20px;
font-family: 'Segoe UI',system-ui,sans-serif;
background: conic-gradient(from 200deg at 50% 50%,#00c2ff,#8e2de2,#ff6a88,#00c2ff);
background-size: 200% 200%;
animation: nb-17-bg 20s ease-in-out infinite;
}
@keyframes nb-17-bg {
50% {
background-position: 100% 100%;
}
}
.nb-17__bar {
position: relative;
display: flex;
padding: 7px;
border-radius: 9999px;
isolation: isolate;
background: color-mix(in oklab,white 14%,transparent);
border: 1px solid rgba(255,255,255,.4);
box-shadow: inset 0 1px 0 rgba(255,255,255,.55),0 20px 44px -24px rgba(0,0,0,.55);
backdrop-filter: blur(22px) saturate(180%);
-webkit-backdrop-filter: blur(22px) saturate(180%);
}
.nb-17__cap {
position: absolute;
top: 7px;
bottom: 7px;
left: 7px;
width: 86px;
border-radius: 9999px;
background: rgba(255,255,255,.26);
border: 1px solid rgba(255,255,255,.55);
box-shadow: inset 0 1px 0 rgba(255,255,255,.7);
z-index: 0;
transition: transform .45s cubic-bezier(.34,1.56,.5,1);
}
.nb-17__r {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
.nb-17__tab {
position: relative;
z-index: 1;
width: 86px;
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 9999px;
cursor: pointer;
color: rgba(255,255,255,.85);
font-size: 14.5px;
font-weight: 600;
transition: color .25s;
}
.nb-17__bar:has(#nb-17-2:checked) .nb-17__cap {
transform: translateX(86px);
}
.nb-17__bar:has(#nb-17-3:checked) .nb-17__cap {
transform: translateX(172px);
}
.nb-17__bar:has(#nb-17-4:checked) .nb-17__cap {
transform: translateX(258px);
}
#nb-17-1:checked~label[for="nb-17-1"],
#nb-17-2:checked~label[for="nb-17-2"],
#nb-17-3:checked~label[for="nb-17-3"],
#nb-17-4:checked~label[for="nb-17-4"] {
color: #fff;
}
.nb-17__r:focus-visible+.nb-17__tab {
outline: 2px solid #fff;
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.nb-17,
.nb-17__cap {
animation: none;
transition: none;
}
}
```
## JavaScript
```js
/* No JavaScript — hidden radio inputs hold the selected tab and CSS :has(#tab:checked) translates the frosted capsule behind it. Arrow keys move between tabs natively. */
```How this works
Each tab is a hidden radio + label pair. A single capsule element is translated with transform:translateX() based on which radio is checked, read via .bar:has(#tab2:checked) selectors — pure CSS state, no JS. The capsule is frosted glass; the labels sit above it.
Radios keep it fully keyboard-operable (arrow keys move between tabs natively), labels are the accessible names, and the capsule is decorative. A :focus-visible ring shows on the focused label.
Make it yours
- Add tabs by extending the radios +
:has()translate rules. - Change capsule easing for a snappier/springier glide.
- Swap horizontal for vertical by translating Y.
Gotchas — read before shipping
- :has() is required for the no-JS version — provide a checked-class JS fallback for old browsers.
- Keep the radios focusable (sr-only clip, not display:none).
- Ensure the capsule width matches the tab width for clean alignment.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.2+ | 121+ | 111+ |
Floor set by :has(), color-mix(), backdrop-filter as this demo is written. The core technique itself goes back further — Chrome 105+ (:has).
:has() drives the no-JS slide; older browsers get a static checked highlight — still fully usable.