32 CSS Tab Designs32 / 32
Inset Type
Active label sits inside a chiseled negative-space well. Inset shadow stack on a darker rectangle gives the impression letters are carved into brass-tinted slate. Pure CSS box-shadow, no images.
Published Updated
The code
<div class="tt20">
<nav class="tt20n">
<input type="radio" name="tt20" id="tt20-r1" checked />
<label class="tt20b" for="tt20-r1">Etch</label>
<input type="radio" name="tt20" id="tt20-r2" />
<label class="tt20b" for="tt20-r2">Engrave</label>
<input type="radio" name="tt20" id="tt20-r3" />
<label class="tt20b" for="tt20-r3">Emboss</label>
</nav>
<div class="tt20p">Acid bites the plate to print recessed lines.</div>
<div class="tt20p">A burin cuts directly into the metal surface.</div>
<div class="tt20p">Pressure raises the paper into a relief.</div>
</div><div class="tt20">
<nav class="tt20n">
<input type="radio" name="tt20" id="tt20-r1" checked />
<label class="tt20b" for="tt20-r1">Etch</label>
<input type="radio" name="tt20" id="tt20-r2" />
<label class="tt20b" for="tt20-r2">Engrave</label>
<input type="radio" name="tt20" id="tt20-r3" />
<label class="tt20b" for="tt20-r3">Emboss</label>
</nav>
<div class="tt20p">Acid bites the plate to print recessed lines.</div>
<div class="tt20p">A burin cuts directly into the metal surface.</div>
<div class="tt20p">Pressure raises the paper into a relief.</div>
</div>.tt20 {
min-height: 100vh;
background: #3a3a42;
padding: 22px 22px 18px;
font-family: ui-monospace, monospace;
width: 100%;
}
.tt20n {
display: flex;
gap: 8px;
padding: 8px;
background: #2a2a30;
border-radius: 4px;
}
.tt20n input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tt20b {
flex: 1;
padding: 14px 12px;
border: 0;
background: transparent;
font: 800 12px/1 ui-monospace, monospace;
letter-spacing: 0.22em;
text-transform: uppercase;
color: rgba(201, 161, 94, 0.6);
cursor: pointer;
border-radius: 3px;
transition: color 0.25s, background 0.3s, box-shadow 0.3s;
}
.tt20b:hover {
color: rgba(201, 161, 94, 0.95);
}
.tt20n input:checked + .tt20b {
background: #1f1f24;
color: #c9a15e;
box-shadow: inset 2px 2px 4px rgba(0, 0, 0, 0.55), inset -1px -1px 2px rgba(255, 255, 255, 0.08);
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.55);
}
.tt20p {
display: none;
padding-top: 14px;
font: italic 11px/1.6 ui-serif, Georgia, serif;
color: rgba(201, 161, 94, 0.8);
}
.tt20:has(#tt20-r1:checked) .tt20p:nth-of-type(1),
.tt20:has(#tt20-r2:checked) .tt20p:nth-of-type(2),
.tt20:has(#tt20-r3:checked) .tt20p:nth-of-type(3) {
display: block;
}.tt20 {
min-height: 100vh;
background: #3a3a42;
padding: 22px 22px 18px;
font-family: ui-monospace, monospace;
width: 100%;
}
.tt20n {
display: flex;
gap: 8px;
padding: 8px;
background: #2a2a30;
border-radius: 4px;
}
.tt20n input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tt20b {
flex: 1;
padding: 14px 12px;
border: 0;
background: transparent;
font: 800 12px/1 ui-monospace, monospace;
letter-spacing: 0.22em;
text-transform: uppercase;
color: rgba(201, 161, 94, 0.6);
cursor: pointer;
border-radius: 3px;
transition: color 0.25s, background 0.3s, box-shadow 0.3s;
}
.tt20b:hover {
color: rgba(201, 161, 94, 0.95);
}
.tt20n input:checked + .tt20b {
background: #1f1f24;
color: #c9a15e;
box-shadow: inset 2px 2px 4px rgba(0, 0, 0, 0.55), inset -1px -1px 2px rgba(255, 255, 255, 0.08);
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.55);
}
.tt20p {
display: none;
padding-top: 14px;
font: italic 11px/1.6 ui-serif, Georgia, serif;
color: rgba(201, 161, 94, 0.8);
}
.tt20:has(#tt20-r1:checked) .tt20p:nth-of-type(1),
.tt20:has(#tt20-r2:checked) .tt20p:nth-of-type(2),
.tt20:has(#tt20-r3:checked) .tt20p:nth-of-type(3) {
display: block;
}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: Inset Type
Source: https://codefronts.com/navigation/css-tabs/inset-type/
Active label sits inside a chiseled negative-space well. Inset shadow stack on a darker rectangle gives the impression letters are carved into brass-tinted slate. Pure CSS box-shadow, no images.
## HTML
```html
<div class="tt20">
<nav class="tt20n">
<input type="radio" name="tt20" id="tt20-r1" checked />
<label class="tt20b" for="tt20-r1">Etch</label>
<input type="radio" name="tt20" id="tt20-r2" />
<label class="tt20b" for="tt20-r2">Engrave</label>
<input type="radio" name="tt20" id="tt20-r3" />
<label class="tt20b" for="tt20-r3">Emboss</label>
</nav>
<div class="tt20p">Acid bites the plate to print recessed lines.</div>
<div class="tt20p">A burin cuts directly into the metal surface.</div>
<div class="tt20p">Pressure raises the paper into a relief.</div>
</div>
```
## CSS
```css
.tt20 {
min-height: 100vh;
background: #3a3a42;
padding: 22px 22px 18px;
font-family: ui-monospace, monospace;
width: 100%;
}
.tt20n {
display: flex;
gap: 8px;
padding: 8px;
background: #2a2a30;
border-radius: 4px;
}
.tt20n input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tt20b {
flex: 1;
padding: 14px 12px;
border: 0;
background: transparent;
font: 800 12px/1 ui-monospace, monospace;
letter-spacing: 0.22em;
text-transform: uppercase;
color: rgba(201, 161, 94, 0.6);
cursor: pointer;
border-radius: 3px;
transition: color 0.25s, background 0.3s, box-shadow 0.3s;
}
.tt20b:hover {
color: rgba(201, 161, 94, 0.95);
}
.tt20n input:checked + .tt20b {
background: #1f1f24;
color: #c9a15e;
box-shadow: inset 2px 2px 4px rgba(0, 0, 0, 0.55), inset -1px -1px 2px rgba(255, 255, 255, 0.08);
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.55);
}
.tt20p {
display: none;
padding-top: 14px;
font: italic 11px/1.6 ui-serif, Georgia, serif;
color: rgba(201, 161, 94, 0.8);
}
.tt20:has(#tt20-r1:checked) .tt20p:nth-of-type(1),
.tt20:has(#tt20-r2:checked) .tt20p:nth-of-type(2),
.tt20:has(#tt20-r3:checked) .tt20p:nth-of-type(3) {
display: block;
}
```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: Inset Type
Source: https://codefronts.com/navigation/css-tabs/inset-type/
Active label sits inside a chiseled negative-space well. Inset shadow stack on a darker rectangle gives the impression letters are carved into brass-tinted slate. Pure CSS box-shadow, no images.
## HTML
```html
<div class="tt20">
<nav class="tt20n">
<input type="radio" name="tt20" id="tt20-r1" checked />
<label class="tt20b" for="tt20-r1">Etch</label>
<input type="radio" name="tt20" id="tt20-r2" />
<label class="tt20b" for="tt20-r2">Engrave</label>
<input type="radio" name="tt20" id="tt20-r3" />
<label class="tt20b" for="tt20-r3">Emboss</label>
</nav>
<div class="tt20p">Acid bites the plate to print recessed lines.</div>
<div class="tt20p">A burin cuts directly into the metal surface.</div>
<div class="tt20p">Pressure raises the paper into a relief.</div>
</div>
```
## CSS
```css
.tt20 {
min-height: 100vh;
background: #3a3a42;
padding: 22px 22px 18px;
font-family: ui-monospace, monospace;
width: 100%;
}
.tt20n {
display: flex;
gap: 8px;
padding: 8px;
background: #2a2a30;
border-radius: 4px;
}
.tt20n input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tt20b {
flex: 1;
padding: 14px 12px;
border: 0;
background: transparent;
font: 800 12px/1 ui-monospace, monospace;
letter-spacing: 0.22em;
text-transform: uppercase;
color: rgba(201, 161, 94, 0.6);
cursor: pointer;
border-radius: 3px;
transition: color 0.25s, background 0.3s, box-shadow 0.3s;
}
.tt20b:hover {
color: rgba(201, 161, 94, 0.95);
}
.tt20n input:checked + .tt20b {
background: #1f1f24;
color: #c9a15e;
box-shadow: inset 2px 2px 4px rgba(0, 0, 0, 0.55), inset -1px -1px 2px rgba(255, 255, 255, 0.08);
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.55);
}
.tt20p {
display: none;
padding-top: 14px;
font: italic 11px/1.6 ui-serif, Georgia, serif;
color: rgba(201, 161, 94, 0.8);
}
.tt20:has(#tt20-r1:checked) .tt20p:nth-of-type(1),
.tt20:has(#tt20-r2:checked) .tt20p:nth-of-type(2),
.tt20:has(#tt20-r3:checked) .tt20p:nth-of-type(3) {
display: block;
}
```How this works
The .tt20 bar is the neumorphic-material register — three brass-tinted mono labels sitting inside a slate #2a2a30 tray on a warmer #3a3a42 background. The active label sits inside a chiseled well: on :checked, the label repaints to brass #c9a15e, its background darkens to #1f1f24, and a two-layer inset box-shadow carves it into the surface. The dark inset inset 2px 2px 4px rgba(0, 0, 0, 0.55) reads as the well's shadow-side; the light inset inset -1px -1px 2px rgba(255, 255, 255, 0.08) reads as the far-lip catching studio light. The illusion is that letters have been struck into brass-tinted slate.
The text-shadow reinforces the depth: 1px 1px 0 rgba(0, 0, 0, 0.55) drops a hard-edge shadow to the bottom-right of each letter, mimicking how carved type reads under raking light. Combined with the 0.22em letter-spacing and text-transform:uppercase on 800-weight monospace, the label reads as chiseled inscription rather than app UI. Inactive labels sit at rgba(201, 161, 94, 0.6) — brass at 60% opacity — with hover lifting to 95%, so the whole nav feels tonally unified across all three states without any color shifting on the tray or wells.
Mechanically it is pure CSS with zero runtime cost. Three hidden radios drive the state, one .tt20b label per option, and a :has()-driven display:block rule reveals the matching .tt20p italic serif caption underneath at rgba(201, 161, 94, 0.8). No JavaScript, no framework, no images — the entire embossed effect is box-shadow and text-shadow layered against the tonal ladder of the tray and well. Copy-paste the semantic HTML radio group, scope your wrapper under .tt20, and the component renders identically in SSR output and on hydration. Framework-agnostic, WCAG 2.1 keyboard-navigable via native radio semantics, and INP-safe because there is no runtime work beyond the CSS transitions themselves.
Make it yours
- Swap brass #c9a15e for silver #c0c0c8, copper #b87333, or oxblood #8b1a1a. Adjust the tray #2a2a30 and well #1f1f24 in lockstep to stay tonally 15-20% darker than the outer .tt20 background — the depth illusion depends on the tonal ladder.
- Retune the well depth by strengthening the dark inset: inset 3px 3px 6px rgba(0, 0, 0, 0.7) gives a deeper carve; inset 1px 1px 2px rgba(0, 0, 0, 0.35) gives a subtle press-in. Keep the light inset at 8-12% opacity or the far-lip highlight over-glows.
- Change the label register: drop text-transform:uppercase and shift to a display serif for a foundry-plaque register. Or lower letter-spacing to 0.1em and switch to a humanist sans for a modern-neumorphic app-nav that still reads as physical material.
- For a light-mode variant, invert everything — outer #e8e6e0, tray #d8d4cc, well #c8c4bc, active label in ink #1a1a1a. Flip the inset colors: dark becomes light-gray inset for the shadow-side, light becomes near-white for the highlight-side.
- Add a subtle inactive press-hover: on .tt20b:hover, apply a very light inset inset 1px 1px 2px rgba(0, 0, 0, 0.2). Reads as the label being ready to be pressed, without committing to the full carved state until the radio is actually checked.
Gotchas — read before shipping
- The inset shadow depth relies on tonal contrast between the tray and the well. If you lighten the tray to #4a4a52 without also lightening the well, the depth illusion collapses because the well and tray read at the same value. Always keep the well 15% darker than the tray, never less.
- The text-shadow at 1px 1px 0 rgba(0, 0, 0, 0.55) is what gives the letters their carved edge. At sub-pixel offsets like 0.5px, browsers render inconsistently — Chrome anti-aliases smoothly, Safari's shadows quantize to 1px. Stick to integer offsets for tier-1 cross-browser parity.
- The brass color #c9a15e against the #1f1f24 well hits about 4.9:1 — passing WCAG 2.1 AA for regular text. If you drop the label size below 12px or shift the brass toward mid-gold like #b88a3e, re-check contrast; the neumorphic aesthetic is a common accessibility failure point.
- @media (prefers-reduced-motion:reduce) is not guarded in the shipped CSS — add it. The 250ms color and 300ms shadow transitions are subtle, but a user on that setting should see an instant swap, not a fade. WCAG 2.1 SC 2.3.3 does not distinguish subtle from dramatic motion.
- The :has()-driven panel reveal degrades on browsers older than Chrome 105 / Safari 15.4 / Firefox 121 by showing every panel stacked (default is display:none, so pre-Baseline browsers see no reveal at all — but no ARIA-live either, which is technically correct fallback for a pure-CSS component).
- Neumorphism is polarizing — the same inset shadow that reads as brass on slate to a design-tuned eye reads as a broken button to some users because the affordance signals pressed rather than press me. Consider pairing with an explicit label like SELECTED or an icon so the state is announced to screen readers via the underlying radio semantics.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 105+ | 15.4+ | 121+ | 105+ |
Panel reveal uses :has(), Baseline 2023-12. The embossed effect itself is box-shadow + text-shadow, which work back to Chrome 10 and Safari 5.1 — feature-detect :has() if you need to serve pre-Baseline tier-2 traffic.