32 CSS Tab Designs03 / 32

Pure CSSMIT licensed

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

Live Demo
Try it

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>
.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;
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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: .tt23 background #fef0c7 and 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 #0a0a0a so 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

ChromeSafariFirefoxEdge
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.

Techniques used in this demo

Search CodeFronts

Loading…