32 CSS Tab Designs15 / 32
Sidebar Nav
Vertical icon-driven nav inspired by hand-built dashboard sidebars. Dusty olive accent bar slides between rows; the active icon shifts left and a label slides in from behind it.
Published Updated
The code
<div class="tt03">
<nav class="tt03n">
<button class="tt03b active" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<path d="M3 11l9-8 9 8v10a2 2 0 0 1-2 2h-4v-7H9v7H5a2 2 0 0 1-2-2z" />
</svg>
<span class="tt03l">Home</span>
</button>
<button class="tt03b" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<path d="M2 12h4l3-9 4 18 3-9h6" />
</svg>
<span class="tt03l">Activity</span>
</button>
<button class="tt03b" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="8" r="4" />
<path d="M3 21v-2a7 7 0 0 1 7-7h4a7 7 0 0 1 7 7v2" />
</svg>
<span class="tt03l">People</span>
</button>
<button class="tt03b" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="3" />
<path
d="M19 12a7 7 0 0 0-.1-1.2l2-1.6-2-3.4-2.5 1a7 7 0 0 0-2.1-1.2L14 3h-4l-.3 2.6a7 7 0 0 0-2.1 1.2l-2.5-1-2 3.4 2 1.6A7 7 0 0 0 5 12c0 .4 0 .8.1 1.2l-2 1.6 2 3.4 2.5-1a7 7 0 0 0 2.1 1.2L10 21h4l.3-2.6a7 7 0 0 0 2.1-1.2l2.5 1 2-3.4-2-1.6c.1-.4.1-.8.1-1.2z"
/>
</svg>
<span class="tt03l">Settings</span>
</button>
<span class="tt03acc" aria-hidden="true"> </span>
</nav>
</div><div class="tt03">
<nav class="tt03n">
<button class="tt03b active" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<path d="M3 11l9-8 9 8v10a2 2 0 0 1-2 2h-4v-7H9v7H5a2 2 0 0 1-2-2z" />
</svg>
<span class="tt03l">Home</span>
</button>
<button class="tt03b" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<path d="M2 12h4l3-9 4 18 3-9h6" />
</svg>
<span class="tt03l">Activity</span>
</button>
<button class="tt03b" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="8" r="4" />
<path d="M3 21v-2a7 7 0 0 1 7-7h4a7 7 0 0 1 7 7v2" />
</svg>
<span class="tt03l">People</span>
</button>
<button class="tt03b" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="3" />
<path
d="M19 12a7 7 0 0 0-.1-1.2l2-1.6-2-3.4-2.5 1a7 7 0 0 0-2.1-1.2L14 3h-4l-.3 2.6a7 7 0 0 0-2.1 1.2l-2.5-1-2 3.4 2 1.6A7 7 0 0 0 5 12c0 .4 0 .8.1 1.2l-2 1.6 2 3.4 2.5-1a7 7 0 0 0 2.1 1.2L10 21h4l.3-2.6a7 7 0 0 0 2.1-1.2l2.5 1 2-3.4-2-1.6c.1-.4.1-.8.1-1.2z"
/>
</svg>
<span class="tt03l">Settings</span>
</button>
<span class="tt03acc" aria-hidden="true"> </span>
</nav>
</div>.tt03 {
background: #e8dac5;
min-height: 100vh;
display: flex;
align-items: stretch;
padding: 12px 0 12px 12px;
font-family: ui-sans-serif, system-ui, sans-serif;
width: 100%;
}
.tt03n {
position: relative;
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
}
.tt03b {
position: relative;
display: flex;
align-items: center;
gap: 14px;
padding: 12px 18px;
border: 0;
background: transparent;
font: 600 13px/1 ui-sans-serif, system-ui;
color: rgba(58, 46, 30, 0.55);
cursor: pointer;
text-align: left;
transition: color 0.25s, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.25s;
}
.tt03b:hover {
color: rgba(58, 46, 30, 0.85);
background: rgba(107, 142, 84, 0.08);
}
.tt03i {
width: 18px;
height: 18px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
flex-shrink: 0;
transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.tt03l {
opacity: 0.7;
transform: translateX(-4px);
transition: opacity 0.3s, transform 0.3s cubic-bezier(0.65, 0, 0.35, 1);
}
.tt03b.active {
color: #4a6a3a;
transform: translateX(4px);
}
.tt03b.active .tt03i {
transform: scale(1.12);
}
.tt03b.active .tt03l {
opacity: 1;
transform: translateX(0);
}
.tt03acc {
position: absolute;
left: -12px;
top: 0;
width: 4px;
height: 0;
background: #6b8e54;
border-radius: 0 4px 4px 0;
transition: top 0.45s cubic-bezier(0.65, 0, 0.35, 1), height 0.45s cubic-bezier(0.65, 0, 0.35, 1);
}.tt03 {
background: #e8dac5;
min-height: 100vh;
display: flex;
align-items: stretch;
padding: 12px 0 12px 12px;
font-family: ui-sans-serif, system-ui, sans-serif;
width: 100%;
}
.tt03n {
position: relative;
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
}
.tt03b {
position: relative;
display: flex;
align-items: center;
gap: 14px;
padding: 12px 18px;
border: 0;
background: transparent;
font: 600 13px/1 ui-sans-serif, system-ui;
color: rgba(58, 46, 30, 0.55);
cursor: pointer;
text-align: left;
transition: color 0.25s, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.25s;
}
.tt03b:hover {
color: rgba(58, 46, 30, 0.85);
background: rgba(107, 142, 84, 0.08);
}
.tt03i {
width: 18px;
height: 18px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
flex-shrink: 0;
transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.tt03l {
opacity: 0.7;
transform: translateX(-4px);
transition: opacity 0.3s, transform 0.3s cubic-bezier(0.65, 0, 0.35, 1);
}
.tt03b.active {
color: #4a6a3a;
transform: translateX(4px);
}
.tt03b.active .tt03i {
transform: scale(1.12);
}
.tt03b.active .tt03l {
opacity: 1;
transform: translateX(0);
}
.tt03acc {
position: absolute;
left: -12px;
top: 0;
width: 4px;
height: 0;
background: #6b8e54;
border-radius: 0 4px 4px 0;
transition: top 0.45s cubic-bezier(0.65, 0, 0.35, 1), height 0.45s cubic-bezier(0.65, 0, 0.35, 1);
}/* Sidebar Nav — toggle .active and slide vertical accent bar.
Re-positions the accent bar on viewport resize. */
(function () {
var nav = document.querySelector(".tt03n");
if (!nav) return;
var btns = nav.querySelectorAll("[data-t]");
var acc = nav.querySelector(".tt03acc");
var current = null;
function reposition() {
if (!current || !acc) return;
acc.style.top = current.offsetTop + "px";
acc.style.height = current.offsetHeight + "px";
}
function activate(btn) {
current = btn;
btns.forEach(function (b) {
b.classList.toggle("active", b === btn);
});
reposition();
}
btns.forEach(function (b) {
b.addEventListener("click", function () {
activate(b);
});
});
window.addEventListener("resize", reposition);
var initial = nav.querySelector("[data-t].active") || btns[0];
if (initial) activate(initial);
})();/* Sidebar Nav — toggle .active and slide vertical accent bar.
Re-positions the accent bar on viewport resize. */
(function () {
var nav = document.querySelector(".tt03n");
if (!nav) return;
var btns = nav.querySelectorAll("[data-t]");
var acc = nav.querySelector(".tt03acc");
var current = null;
function reposition() {
if (!current || !acc) return;
acc.style.top = current.offsetTop + "px";
acc.style.height = current.offsetHeight + "px";
}
function activate(btn) {
current = btn;
btns.forEach(function (b) {
b.classList.toggle("active", b === btn);
});
reposition();
}
btns.forEach(function (b) {
b.addEventListener("click", function () {
activate(b);
});
});
window.addEventListener("resize", reposition);
var initial = nav.querySelector("[data-t].active") || btns[0];
if (initial) activate(initial);
})();Here's a working CSS Tab Design 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: Sidebar Nav
Source: https://codefronts.com/navigation/css-tabs/sidebar-nav/
Vertical icon-driven nav inspired by hand-built dashboard sidebars. Dusty olive accent bar slides between rows; the active icon shifts left and a label slides in from behind it.
## HTML
```html
<div class="tt03">
<nav class="tt03n">
<button class="tt03b active" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<path d="M3 11l9-8 9 8v10a2 2 0 0 1-2 2h-4v-7H9v7H5a2 2 0 0 1-2-2z" />
</svg>
<span class="tt03l">Home</span>
</button>
<button class="tt03b" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<path d="M2 12h4l3-9 4 18 3-9h6" />
</svg>
<span class="tt03l">Activity</span>
</button>
<button class="tt03b" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="8" r="4" />
<path d="M3 21v-2a7 7 0 0 1 7-7h4a7 7 0 0 1 7 7v2" />
</svg>
<span class="tt03l">People</span>
</button>
<button class="tt03b" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="3" />
<path
d="M19 12a7 7 0 0 0-.1-1.2l2-1.6-2-3.4-2.5 1a7 7 0 0 0-2.1-1.2L14 3h-4l-.3 2.6a7 7 0 0 0-2.1 1.2l-2.5-1-2 3.4 2 1.6A7 7 0 0 0 5 12c0 .4 0 .8.1 1.2l-2 1.6 2 3.4 2.5-1a7 7 0 0 0 2.1 1.2L10 21h4l.3-2.6a7 7 0 0 0 2.1-1.2l2.5 1 2-3.4-2-1.6c.1-.4.1-.8.1-1.2z"
/>
</svg>
<span class="tt03l">Settings</span>
</button>
<span class="tt03acc" aria-hidden="true"> </span>
</nav>
</div>
```
## CSS
```css
.tt03 {
background: #e8dac5;
min-height: 100vh;
display: flex;
align-items: stretch;
padding: 12px 0 12px 12px;
font-family: ui-sans-serif, system-ui, sans-serif;
width: 100%;
}
.tt03n {
position: relative;
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
}
.tt03b {
position: relative;
display: flex;
align-items: center;
gap: 14px;
padding: 12px 18px;
border: 0;
background: transparent;
font: 600 13px/1 ui-sans-serif, system-ui;
color: rgba(58, 46, 30, 0.55);
cursor: pointer;
text-align: left;
transition: color 0.25s, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.25s;
}
.tt03b:hover {
color: rgba(58, 46, 30, 0.85);
background: rgba(107, 142, 84, 0.08);
}
.tt03i {
width: 18px;
height: 18px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
flex-shrink: 0;
transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.tt03l {
opacity: 0.7;
transform: translateX(-4px);
transition: opacity 0.3s, transform 0.3s cubic-bezier(0.65, 0, 0.35, 1);
}
.tt03b.active {
color: #4a6a3a;
transform: translateX(4px);
}
.tt03b.active .tt03i {
transform: scale(1.12);
}
.tt03b.active .tt03l {
opacity: 1;
transform: translateX(0);
}
.tt03acc {
position: absolute;
left: -12px;
top: 0;
width: 4px;
height: 0;
background: #6b8e54;
border-radius: 0 4px 4px 0;
transition: top 0.45s cubic-bezier(0.65, 0, 0.35, 1), height 0.45s cubic-bezier(0.65, 0, 0.35, 1);
}
```
## JavaScript
```js
/* Sidebar Nav — toggle .active and slide vertical accent bar.
Re-positions the accent bar on viewport resize. */
(function () {
var nav = document.querySelector(".tt03n");
if (!nav) return;
var btns = nav.querySelectorAll("[data-t]");
var acc = nav.querySelector(".tt03acc");
var current = null;
function reposition() {
if (!current || !acc) return;
acc.style.top = current.offsetTop + "px";
acc.style.height = current.offsetHeight + "px";
}
function activate(btn) {
current = btn;
btns.forEach(function (b) {
b.classList.toggle("active", b === btn);
});
reposition();
}
btns.forEach(function (b) {
b.addEventListener("click", function () {
activate(b);
});
});
window.addEventListener("resize", reposition);
var initial = nav.querySelector("[data-t].active") || btns[0];
if (initial) activate(initial);
})();
```Here's a working CSS Tab Design 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: Sidebar Nav
Source: https://codefronts.com/navigation/css-tabs/sidebar-nav/
Vertical icon-driven nav inspired by hand-built dashboard sidebars. Dusty olive accent bar slides between rows; the active icon shifts left and a label slides in from behind it.
## HTML
```html
<div class="tt03">
<nav class="tt03n">
<button class="tt03b active" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<path d="M3 11l9-8 9 8v10a2 2 0 0 1-2 2h-4v-7H9v7H5a2 2 0 0 1-2-2z" />
</svg>
<span class="tt03l">Home</span>
</button>
<button class="tt03b" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<path d="M2 12h4l3-9 4 18 3-9h6" />
</svg>
<span class="tt03l">Activity</span>
</button>
<button class="tt03b" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="8" r="4" />
<path d="M3 21v-2a7 7 0 0 1 7-7h4a7 7 0 0 1 7 7v2" />
</svg>
<span class="tt03l">People</span>
</button>
<button class="tt03b" data-t>
<svg class="tt03i" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="3" />
<path
d="M19 12a7 7 0 0 0-.1-1.2l2-1.6-2-3.4-2.5 1a7 7 0 0 0-2.1-1.2L14 3h-4l-.3 2.6a7 7 0 0 0-2.1 1.2l-2.5-1-2 3.4 2 1.6A7 7 0 0 0 5 12c0 .4 0 .8.1 1.2l-2 1.6 2 3.4 2.5-1a7 7 0 0 0 2.1 1.2L10 21h4l.3-2.6a7 7 0 0 0 2.1-1.2l2.5 1 2-3.4-2-1.6c.1-.4.1-.8.1-1.2z"
/>
</svg>
<span class="tt03l">Settings</span>
</button>
<span class="tt03acc" aria-hidden="true"> </span>
</nav>
</div>
```
## CSS
```css
.tt03 {
background: #e8dac5;
min-height: 100vh;
display: flex;
align-items: stretch;
padding: 12px 0 12px 12px;
font-family: ui-sans-serif, system-ui, sans-serif;
width: 100%;
}
.tt03n {
position: relative;
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
}
.tt03b {
position: relative;
display: flex;
align-items: center;
gap: 14px;
padding: 12px 18px;
border: 0;
background: transparent;
font: 600 13px/1 ui-sans-serif, system-ui;
color: rgba(58, 46, 30, 0.55);
cursor: pointer;
text-align: left;
transition: color 0.25s, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.25s;
}
.tt03b:hover {
color: rgba(58, 46, 30, 0.85);
background: rgba(107, 142, 84, 0.08);
}
.tt03i {
width: 18px;
height: 18px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
flex-shrink: 0;
transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.tt03l {
opacity: 0.7;
transform: translateX(-4px);
transition: opacity 0.3s, transform 0.3s cubic-bezier(0.65, 0, 0.35, 1);
}
.tt03b.active {
color: #4a6a3a;
transform: translateX(4px);
}
.tt03b.active .tt03i {
transform: scale(1.12);
}
.tt03b.active .tt03l {
opacity: 1;
transform: translateX(0);
}
.tt03acc {
position: absolute;
left: -12px;
top: 0;
width: 4px;
height: 0;
background: #6b8e54;
border-radius: 0 4px 4px 0;
transition: top 0.45s cubic-bezier(0.65, 0, 0.35, 1), height 0.45s cubic-bezier(0.65, 0, 0.35, 1);
}
```
## JavaScript
```js
/* Sidebar Nav — toggle .active and slide vertical accent bar.
Re-positions the accent bar on viewport resize. */
(function () {
var nav = document.querySelector(".tt03n");
if (!nav) return;
var btns = nav.querySelectorAll("[data-t]");
var acc = nav.querySelector(".tt03acc");
var current = null;
function reposition() {
if (!current || !acc) return;
acc.style.top = current.offsetTop + "px";
acc.style.height = current.offsetHeight + "px";
}
function activate(btn) {
current = btn;
btns.forEach(function (b) {
b.classList.toggle("active", b === btn);
});
reposition();
}
btns.forEach(function (b) {
b.addEventListener("click", function () {
activate(b);
});
});
window.addEventListener("resize", reposition);
var initial = nav.querySelector("[data-t].active") || btns[0];
if (initial) activate(initial);
})();
```How this works
The sidebar-nav tabs render as a vertical .tt03n flex column of four button rows, each holding an inline SVG icon and a .tt03l text label. The container sits on a warm #e8dac5 stone background with 12px left padding that reserves space for the accent bar. The bar itself is a .tt03acc span at left:-12px, 4px wide with a rounded right edge, coloured olive #6b8e54. Icons stroke at 1.8px with currentColor, so their fill inherits from the button's colour state.
The IIFE queries .tt03n, caches the buttons and the accent span, and defines reposition plus activate. Every click toggles .active across all buttons and sets the accent bar's top to the current button's offsetTop and height to its offsetHeight, so the bar slides vertically to hug the active row. Both properties transition 450ms with cubic-bezier(0.65,0,0.35,1). A resize listener re-anchors the bar when the column reflows.
The active state layers three motions. .tt03b.active shifts the whole button 4px right with a spring cubic-bezier(0.34,1.56,0.64,1) that overshoots slightly, so the row feels pressed toward the reader. Its icon scales to 1.12 in 450ms, and the .tt03l label shifts from translateX(-4px) with opacity 0.7 to translateX(0) at full opacity in 300ms — the label appears to slide out from behind the icon rather than fading in place. Colour lifts from rgba(58,46,30,0.55) to deep olive #4a6a3a. Hover paints a warm rgba(107,142,84,0.08) wash so the row acknowledges the pointer without competing with the active accent. There is no explicit prefers-reduced-motion block — add one that neutralises the spring transforms for INP-safe delivery.
Make it yours
- Swap the olive palette by replacing #6b8e54 on the accent bar, #4a6a3a on the active text, and the hover wash rgba(107,142,84,0.08). A cool slate variant with #4a6a80 works against the same #e8dac5 stone ground.
- Retune the accent slide by editing the two 0.45s durations on .tt03acc. A 250ms bar feels responsive for a docs tree with 20+ items; 600ms suits a settings sidebar with 4-6 items where each transition should register.
- Change the accent bar geometry by editing width:4px and border-radius:0 4px 4px 0. A 6px bar with square corners reads as a print underline; a 2px bar with pill radius reads as a modern app rail.
- Replace the SVG glyphs with your icon set by editing the path data. Keep the 24-viewBox and 1.8 stroke width so the scale-1.12 active state does not need retuning; heavier icon sets should compensate by dropping the active scale to 1.06.
- Add a dark-mode variant by swapping #e8dac5 for #1a1815 and lifting the inactive text alpha to rgba(232,226,208,0.65). The olive accent still sings on charcoal, and the hover wash reads if you raise its alpha to 0.14.
Gotchas — read before shipping
- The nav uses button elements with no ARIA tab semantics or tabpanel wiring. For a docs-site nav that swaps content on click, add role="tab", aria-selected, and linked role="tabpanel" containers, or rebrand as a role="navigation" link list if buttons drive routing.
- Vertical tab patterns need up-arrow and down-arrow keyboard handling per WAI-ARIA. This demo has none — Tab moves between buttons but arrow keys do nothing. Add a keydown handler that moves focus and manages tabindex="-1" on inactive tabs.
- The inactive label colour rgba(58,46,30,0.55) on #e8dac5 sits at roughly 3.6:1 contrast, under WCAG 2.1 AA for normal text. Raise to 0.75 alpha or drop the ground to a warmer #dccca8 to clear 4.5:1.
- The resize listener attaches to window globally and never detaches. React StrictMode double-mounts leak two listeners; unmounting the nav orphans the closure. Wrap in a mount lifecycle and remove the listener on cleanup for SSR-safe behaviour.
- There is no prefers-reduced-motion:reduce block. The spring cubic-bezier transforms on active buttons and icons still fire for users with vestibular sensitivities. Add a block that sets transition:none on .tt03b, .tt03i, .tt03l, and .tt03acc.
- The querySelector(".tt03n") and its descendants are global. Two sidebar-nav instances on the same page share the same script call and only the first one gets wired. Refactor the IIFE to accept a root element and call it per instance, or scope class names with a suffix.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 60+ | 12+ | 60+ | 60+ |
Everything used is baseline: flexbox, CSS transitions, inline SVG with currentColor. No :has(), no container queries, no custom-property tricks in keyframes. Safe to ship framework-agnostic; verify your JS build targets the same era if you bundle the IIFE.