32 CSS Tab Designs03 / 32
Brutalist Press
Heavy uppercase tabs with hard 4px black offset shadows. Active tab presses flush — shadow collapses, button translates 4px down-right, color inverts. Tactile mechanical-keyboard feedback.
Published
The code
<div class="tt23">
<nav class="tt23n">
<input type="radio" name="tt23" id="tt23-r1" checked />
<label class="tt23b" for="tt23-r1">Build</label>
<input type="radio" name="tt23" id="tt23-r2" />
<label class="tt23b" for="tt23-r2">Ship</label>
<input type="radio" name="tt23" id="tt23-r3" />
<label class="tt23b" for="tt23-r3">Iterate</label>
</nav>
<div class="tt23p">Build — assemble the parts.</div>
<div class="tt23p">Ship — push it to production.</div>
<div class="tt23p">Iterate — measure and refine.</div>
</div><div class="tt23">
<nav class="tt23n">
<input type="radio" name="tt23" id="tt23-r1" checked />
<label class="tt23b" for="tt23-r1">Build</label>
<input type="radio" name="tt23" id="tt23-r2" />
<label class="tt23b" for="tt23-r2">Ship</label>
<input type="radio" name="tt23" id="tt23-r3" />
<label class="tt23b" for="tt23-r3">Iterate</label>
</nav>
<div class="tt23p">Build — assemble the parts.</div>
<div class="tt23p">Ship — push it to production.</div>
<div class="tt23p">Iterate — measure and refine.</div>
</div>.tt23 {
background: #fef0c7;
padding: 26px 22px;
font-family: ui-sans-serif, system-ui, sans-serif;
min-height: 100vh;
box-sizing: border-box;
width: 100%;
}
.tt23n {
display: flex;
gap: 16px;
flex-wrap: wrap;
min-width: 0;
}
.tt23n input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tt23b {
flex: 1;
min-width: 0;
padding: 12px 16px;
border: 3px solid #0a0a0a;
background: #fff;
font: 800 12px/1 ui-sans-serif, system-ui;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #0a0a0a;
cursor: pointer;
text-align: center;
box-shadow: 4px 4px 0 0 #0a0a0a;
transition: transform 0.12s ease-out, box-shadow 0.12s ease-out, background 0.2s, color 0.2s;
white-space: nowrap;
}
.tt23b:hover {
background: #f4e7b3;
}
.tt23n input:checked + .tt23b {
transform: translate(4px, 4px);
box-shadow: 0 0 0 0 #0a0a0a;
background: #0a0a0a;
color: #fef0c7;
}
.tt23n input:focus-visible + .tt23b {
outline: 2px dashed #0a0a0a;
outline-offset: 4px;
}
.tt23p {
display: none;
padding-top: 22px;
font: 600 13px/1.55 ui-sans-serif, system-ui;
color: #0a0a0a;
}
.tt23:has(#tt23-r1:checked) .tt23p:nth-of-type(1),
.tt23:has(#tt23-r2:checked) .tt23p:nth-of-type(2),
.tt23:has(#tt23-r3:checked) .tt23p:nth-of-type(3) {
display: block;
}.tt23 {
background: #fef0c7;
padding: 26px 22px;
font-family: ui-sans-serif, system-ui, sans-serif;
min-height: 100vh;
box-sizing: border-box;
width: 100%;
}
.tt23n {
display: flex;
gap: 16px;
flex-wrap: wrap;
min-width: 0;
}
.tt23n input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tt23b {
flex: 1;
min-width: 0;
padding: 12px 16px;
border: 3px solid #0a0a0a;
background: #fff;
font: 800 12px/1 ui-sans-serif, system-ui;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #0a0a0a;
cursor: pointer;
text-align: center;
box-shadow: 4px 4px 0 0 #0a0a0a;
transition: transform 0.12s ease-out, box-shadow 0.12s ease-out, background 0.2s, color 0.2s;
white-space: nowrap;
}
.tt23b:hover {
background: #f4e7b3;
}
.tt23n input:checked + .tt23b {
transform: translate(4px, 4px);
box-shadow: 0 0 0 0 #0a0a0a;
background: #0a0a0a;
color: #fef0c7;
}
.tt23n input:focus-visible + .tt23b {
outline: 2px dashed #0a0a0a;
outline-offset: 4px;
}
.tt23p {
display: none;
padding-top: 22px;
font: 600 13px/1.55 ui-sans-serif, system-ui;
color: #0a0a0a;
}
.tt23:has(#tt23-r1:checked) .tt23p:nth-of-type(1),
.tt23:has(#tt23-r2:checked) .tt23p:nth-of-type(2),
.tt23:has(#tt23-r3:checked) .tt23p: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: Brutalist Press
Source: https://codefronts.com/navigation/css-tabs/brutalist-press/
Heavy uppercase tabs with hard 4px black offset shadows. Active tab presses flush — shadow collapses, button translates 4px down-right, color inverts. Tactile mechanical-keyboard feedback.
## HTML
```html
<div class="tt23">
<nav class="tt23n">
<input type="radio" name="tt23" id="tt23-r1" checked />
<label class="tt23b" for="tt23-r1">Build</label>
<input type="radio" name="tt23" id="tt23-r2" />
<label class="tt23b" for="tt23-r2">Ship</label>
<input type="radio" name="tt23" id="tt23-r3" />
<label class="tt23b" for="tt23-r3">Iterate</label>
</nav>
<div class="tt23p">Build — assemble the parts.</div>
<div class="tt23p">Ship — push it to production.</div>
<div class="tt23p">Iterate — measure and refine.</div>
</div>
```
## CSS
```css
.tt23 {
background: #fef0c7;
padding: 26px 22px;
font-family: ui-sans-serif, system-ui, sans-serif;
min-height: 100vh;
box-sizing: border-box;
width: 100%;
}
.tt23n {
display: flex;
gap: 16px;
flex-wrap: wrap;
min-width: 0;
}
.tt23n input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tt23b {
flex: 1;
min-width: 0;
padding: 12px 16px;
border: 3px solid #0a0a0a;
background: #fff;
font: 800 12px/1 ui-sans-serif, system-ui;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #0a0a0a;
cursor: pointer;
text-align: center;
box-shadow: 4px 4px 0 0 #0a0a0a;
transition: transform 0.12s ease-out, box-shadow 0.12s ease-out, background 0.2s, color 0.2s;
white-space: nowrap;
}
.tt23b:hover {
background: #f4e7b3;
}
.tt23n input:checked + .tt23b {
transform: translate(4px, 4px);
box-shadow: 0 0 0 0 #0a0a0a;
background: #0a0a0a;
color: #fef0c7;
}
.tt23n input:focus-visible + .tt23b {
outline: 2px dashed #0a0a0a;
outline-offset: 4px;
}
.tt23p {
display: none;
padding-top: 22px;
font: 600 13px/1.55 ui-sans-serif, system-ui;
color: #0a0a0a;
}
.tt23:has(#tt23-r1:checked) .tt23p:nth-of-type(1),
.tt23:has(#tt23-r2:checked) .tt23p:nth-of-type(2),
.tt23:has(#tt23-r3:checked) .tt23p: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: Brutalist Press
Source: https://codefronts.com/navigation/css-tabs/brutalist-press/
Heavy uppercase tabs with hard 4px black offset shadows. Active tab presses flush — shadow collapses, button translates 4px down-right, color inverts. Tactile mechanical-keyboard feedback.
## HTML
```html
<div class="tt23">
<nav class="tt23n">
<input type="radio" name="tt23" id="tt23-r1" checked />
<label class="tt23b" for="tt23-r1">Build</label>
<input type="radio" name="tt23" id="tt23-r2" />
<label class="tt23b" for="tt23-r2">Ship</label>
<input type="radio" name="tt23" id="tt23-r3" />
<label class="tt23b" for="tt23-r3">Iterate</label>
</nav>
<div class="tt23p">Build — assemble the parts.</div>
<div class="tt23p">Ship — push it to production.</div>
<div class="tt23p">Iterate — measure and refine.</div>
</div>
```
## CSS
```css
.tt23 {
background: #fef0c7;
padding: 26px 22px;
font-family: ui-sans-serif, system-ui, sans-serif;
min-height: 100vh;
box-sizing: border-box;
width: 100%;
}
.tt23n {
display: flex;
gap: 16px;
flex-wrap: wrap;
min-width: 0;
}
.tt23n input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tt23b {
flex: 1;
min-width: 0;
padding: 12px 16px;
border: 3px solid #0a0a0a;
background: #fff;
font: 800 12px/1 ui-sans-serif, system-ui;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #0a0a0a;
cursor: pointer;
text-align: center;
box-shadow: 4px 4px 0 0 #0a0a0a;
transition: transform 0.12s ease-out, box-shadow 0.12s ease-out, background 0.2s, color 0.2s;
white-space: nowrap;
}
.tt23b:hover {
background: #f4e7b3;
}
.tt23n input:checked + .tt23b {
transform: translate(4px, 4px);
box-shadow: 0 0 0 0 #0a0a0a;
background: #0a0a0a;
color: #fef0c7;
}
.tt23n input:focus-visible + .tt23b {
outline: 2px dashed #0a0a0a;
outline-offset: 4px;
}
.tt23p {
display: none;
padding-top: 22px;
font: 600 13px/1.55 ui-sans-serif, system-ui;
color: #0a0a0a;
}
.tt23:has(#tt23-r1:checked) .tt23p:nth-of-type(1),
.tt23:has(#tt23-r2:checked) .tt23p:nth-of-type(2),
.tt23:has(#tt23-r3:checked) .tt23p:nth-of-type(3) {
display: block;
}
```How this works
The .tt23 shell paints a warm brutalist yellow #fef0c7 canvas with generous 26px 22px padding, and .tt23n is a flex row with a chunky gap: 16px. Each tab is a hidden radio plus a .tt23b label with a 3px solid #0a0a0a border, white fill, and a hard box-shadow: 4px 4px 0 0 #0a0a0a offset — no blur, no spread, pure neo-brutalist offset. The press effect is one line: .tt23n input:checked + .tt23b flips transform: translate(4px, 4px) and collapses the shadow to 0 0 0 0, so the button visually slides into where its own shadow used to be. Color inverts to #fef0c7 ink on #0a0a0a ground.
The palette is Fly.io and Prisma adjacent: heavy warning yellow, absolute black ink, and no soft mid-tones. Type is ui-sans-serif, system-ui at 800 12px/1 with letter-spacing: 0.12em and text-transform: uppercase — a stencil register that carries the mechanical-keyboard metaphor. Hover paints a subtler #f4e7b3 yellow so users get a preview of the press without committing to the state flip.
Semantic radio inputs mean keyboard drivers get Space and arrow-key navigation for free, and the pattern is pure CSS with no hydration or measurement — INP is a single paint, CLS is zero because the offset is transform-only. The focus ring uses outline: 2px dashed #0a0a0a with outline-offset: 4px so it sits clear of the shadow, satisfying WCAG 2.4.7 and 2.5.5. The 0.12s transform and box-shadow transitions are short enough to read as tactile rather than animated, but a @media (prefers-reduced-motion: reduce) override is still the honest move for WCAG 2.3.3. For a real tabs pattern, add role="tablist" on the nav and role="tabpanel" around each .tt23p.
Make it yours
- Swap the yellow:
.tt23background#fef0c7and the checked label color read from the same warning-yellow token. Try safety orange#fed7aa, mint#bbf7d0, or a punk pink#fce7f3— keep the ink at#0a0a0aso the press metaphor holds. - Type swap: drop in a display face like font: 800 13px/1 Space Grotesk, ui-sans-serif, or push weight to 900 with a wider letter-spacing: 0.16em for a stronger stencil register.
- Shape and shadow: change the offset to 6px 6px 0 0 and match the translate to translate(6px, 6px) for a heavier press, or add border-radius: 4px for a softened brutalist take.
- Dark variant: flip .tt23 to #0a0a0a, borders and shadow to #fef0c7, resting label fill to #0a0a0a with yellow ink; the checked state inverts back to black-on-yellow.
- Tailwind arbitrary values: shadow-[4px_4px_0_0_#0a0a0a] at rest, checked variant with peer-checked:translate-x-1 peer-checked:translate-y-1 peer-checked:shadow-none, and border-[3px] border-[#0a0a0a] on the label.
Gotchas — read before shipping
- Yellow #fef0c7 on #0a0a0a ink for the pressed state passes WCAG 1.4.3 comfortably; the resting #0a0a0a label on white does too. Verify hover #f4e7b3 if you tune the palette — some yellows drop close to the 4.5:1 line under 12px.
- The transform: translate(4px, 4px) press does not update layout, but if you nest tabs inside a will-change: transform parent Safari may promote the layer and blur the hard shadow edge. Keep will-change off the parent or apply it to the label directly.
- Radios auto-group but do not announce as tabs. For the ARIA tabs pattern add role="tablist", role="tab", aria-selected, role="tabpanel", and Left/Right arrow-key handling with roving tabindex — WCAG 2.1.1 and 4.1.2.
- The 0.12s press transition passes most vestibular thresholds but still counts as motion. Add @media (prefers-reduced-motion: reduce) so users who opt out get instant state flips per WCAG 2.3.3.
- React StrictMode double-mounts controlled radios, and if you tie the checked state to useState without a stable key, the initial pressed tab can flash unpressed on rehydration. Use defaultChecked for uncontrolled radios or hydrate from a server-computed prop.
- Firefox 107 and earlier render box-shadow: 0 0 0 0 without collapsing subpixel artifacts on some GPUs, so a faint ghost line can remain at the pressed corner. Firefox 121 fixed the rounding; if you support older, switch to box-shadow: none in the checked state.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 105+ | 15.4+ | 121+ | 105+ |
Floor set by :has() as this demo is written. The core technique itself goes back further — Chrome 90+.
Pure CSS radio + :has() panel switching; :has() needs Firefox 121+ if you want the panel to swap without JS.