32 CSS Tab Designs27 / 32
Slab Serif Swash
Italic slab-serif labels on warm-cream stock. The active tab gets a deep-emerald swash underline — a curved SVG stroke beneath the word, like a calligrapher's flourish on letterpress vellum.
Published Updated
The code
<div class="tt15">
<nav class="tt15n">
<input type="radio" name="tt15" id="tt15-r1" checked />
<label class="tt15b" for="tt15-r1">
Manifesto
<svg class="tt15s" viewBox="0 0 80 12" aria-hidden="true">
<path
d="M 2 8 Q 20 2 40 6 T 78 8"
fill="none"
stroke="#0f4f3d"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</label>
<input type="radio" name="tt15" id="tt15-r2" />
<label class="tt15b" for="tt15-r2">
Colophon
<svg class="tt15s" viewBox="0 0 80 12" aria-hidden="true">
<path
d="M 2 8 Q 20 2 40 6 T 78 8"
fill="none"
stroke="#0f4f3d"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</label>
<input type="radio" name="tt15" id="tt15-r3" />
<label class="tt15b" for="tt15-r3">
Errata
<svg class="tt15s" viewBox="0 0 80 12" aria-hidden="true">
<path
d="M 2 8 Q 20 2 40 6 T 78 8"
fill="none"
stroke="#0f4f3d"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</label>
</nav>
<div class="tt15p">The founding statement of intent.</div>
<div class="tt15p">The closing inscription, type & paper notes.</div>
<div class="tt15p">Corrections issued after the first printing.</div>
</div><div class="tt15">
<nav class="tt15n">
<input type="radio" name="tt15" id="tt15-r1" checked />
<label class="tt15b" for="tt15-r1">
Manifesto
<svg class="tt15s" viewBox="0 0 80 12" aria-hidden="true">
<path
d="M 2 8 Q 20 2 40 6 T 78 8"
fill="none"
stroke="#0f4f3d"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</label>
<input type="radio" name="tt15" id="tt15-r2" />
<label class="tt15b" for="tt15-r2">
Colophon
<svg class="tt15s" viewBox="0 0 80 12" aria-hidden="true">
<path
d="M 2 8 Q 20 2 40 6 T 78 8"
fill="none"
stroke="#0f4f3d"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</label>
<input type="radio" name="tt15" id="tt15-r3" />
<label class="tt15b" for="tt15-r3">
Errata
<svg class="tt15s" viewBox="0 0 80 12" aria-hidden="true">
<path
d="M 2 8 Q 20 2 40 6 T 78 8"
fill="none"
stroke="#0f4f3d"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</label>
</nav>
<div class="tt15p">The founding statement of intent.</div>
<div class="tt15p">The closing inscription, type & paper notes.</div>
<div class="tt15p">Corrections issued after the first printing.</div>
</div>.tt15 {
min-height: 100vh;
background: #fdf4d6;
padding: 22px 22px 26px;
font-family: ui-serif, "Times New Roman", serif;
width: 100%;
}
.tt15n {
display: flex;
gap: 28px;
align-items: flex-end;
}
.tt15n input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tt15b {
position: relative;
border: 0;
background: transparent;
padding: 0 0 4px;
font: italic 700 18px/1 ui-serif, "Times New Roman", serif;
color: rgba(15, 79, 61, 0.55);
cursor: pointer;
transition: color 0.25s;
}
.tt15b:hover {
color: rgba(15, 79, 61, 0.9);
}
.tt15n input:checked + .tt15b {
color: #0f4f3d;
}
.tt15s {
position: absolute;
bottom: -8px;
left: 0;
right: 0;
width: 100%;
height: 12px;
opacity: 0;
stroke-dasharray: 100;
stroke-dashoffset: 100;
transition: opacity 0.2s, stroke-dashoffset 0.6s cubic-bezier(0.65, 0, 0.35, 1);
}
.tt15n input:checked + .tt15b .tt15s {
opacity: 1;
stroke-dashoffset: 0;
}
.tt15p {
display: none;
padding-top: 18px;
font: italic 12px/1.6 ui-serif, Georgia;
color: #3a3a42;
}
.tt15:has(#tt15-r1:checked) .tt15p:nth-of-type(1),
.tt15:has(#tt15-r2:checked) .tt15p:nth-of-type(2),
.tt15:has(#tt15-r3:checked) .tt15p:nth-of-type(3) {
display: block;
}.tt15 {
min-height: 100vh;
background: #fdf4d6;
padding: 22px 22px 26px;
font-family: ui-serif, "Times New Roman", serif;
width: 100%;
}
.tt15n {
display: flex;
gap: 28px;
align-items: flex-end;
}
.tt15n input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tt15b {
position: relative;
border: 0;
background: transparent;
padding: 0 0 4px;
font: italic 700 18px/1 ui-serif, "Times New Roman", serif;
color: rgba(15, 79, 61, 0.55);
cursor: pointer;
transition: color 0.25s;
}
.tt15b:hover {
color: rgba(15, 79, 61, 0.9);
}
.tt15n input:checked + .tt15b {
color: #0f4f3d;
}
.tt15s {
position: absolute;
bottom: -8px;
left: 0;
right: 0;
width: 100%;
height: 12px;
opacity: 0;
stroke-dasharray: 100;
stroke-dashoffset: 100;
transition: opacity 0.2s, stroke-dashoffset 0.6s cubic-bezier(0.65, 0, 0.35, 1);
}
.tt15n input:checked + .tt15b .tt15s {
opacity: 1;
stroke-dashoffset: 0;
}
.tt15p {
display: none;
padding-top: 18px;
font: italic 12px/1.6 ui-serif, Georgia;
color: #3a3a42;
}
.tt15:has(#tt15-r1:checked) .tt15p:nth-of-type(1),
.tt15:has(#tt15-r2:checked) .tt15p:nth-of-type(2),
.tt15:has(#tt15-r3:checked) .tt15p: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: Slab Serif Swash
Source: https://codefronts.com/navigation/css-tabs/slab-serif-swash/
Italic slab-serif labels on warm-cream stock. The active tab gets a deep-emerald swash underline — a curved SVG stroke beneath the word, like a calligrapher's flourish on letterpress vellum.
## HTML
```html
<div class="tt15">
<nav class="tt15n">
<input type="radio" name="tt15" id="tt15-r1" checked />
<label class="tt15b" for="tt15-r1">
Manifesto
<svg class="tt15s" viewBox="0 0 80 12" aria-hidden="true">
<path
d="M 2 8 Q 20 2 40 6 T 78 8"
fill="none"
stroke="#0f4f3d"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</label>
<input type="radio" name="tt15" id="tt15-r2" />
<label class="tt15b" for="tt15-r2">
Colophon
<svg class="tt15s" viewBox="0 0 80 12" aria-hidden="true">
<path
d="M 2 8 Q 20 2 40 6 T 78 8"
fill="none"
stroke="#0f4f3d"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</label>
<input type="radio" name="tt15" id="tt15-r3" />
<label class="tt15b" for="tt15-r3">
Errata
<svg class="tt15s" viewBox="0 0 80 12" aria-hidden="true">
<path
d="M 2 8 Q 20 2 40 6 T 78 8"
fill="none"
stroke="#0f4f3d"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</label>
</nav>
<div class="tt15p">The founding statement of intent.</div>
<div class="tt15p">The closing inscription, type & paper notes.</div>
<div class="tt15p">Corrections issued after the first printing.</div>
</div>
```
## CSS
```css
.tt15 {
min-height: 100vh;
background: #fdf4d6;
padding: 22px 22px 26px;
font-family: ui-serif, "Times New Roman", serif;
width: 100%;
}
.tt15n {
display: flex;
gap: 28px;
align-items: flex-end;
}
.tt15n input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tt15b {
position: relative;
border: 0;
background: transparent;
padding: 0 0 4px;
font: italic 700 18px/1 ui-serif, "Times New Roman", serif;
color: rgba(15, 79, 61, 0.55);
cursor: pointer;
transition: color 0.25s;
}
.tt15b:hover {
color: rgba(15, 79, 61, 0.9);
}
.tt15n input:checked + .tt15b {
color: #0f4f3d;
}
.tt15s {
position: absolute;
bottom: -8px;
left: 0;
right: 0;
width: 100%;
height: 12px;
opacity: 0;
stroke-dasharray: 100;
stroke-dashoffset: 100;
transition: opacity 0.2s, stroke-dashoffset 0.6s cubic-bezier(0.65, 0, 0.35, 1);
}
.tt15n input:checked + .tt15b .tt15s {
opacity: 1;
stroke-dashoffset: 0;
}
.tt15p {
display: none;
padding-top: 18px;
font: italic 12px/1.6 ui-serif, Georgia;
color: #3a3a42;
}
.tt15:has(#tt15-r1:checked) .tt15p:nth-of-type(1),
.tt15:has(#tt15-r2:checked) .tt15p:nth-of-type(2),
.tt15:has(#tt15-r3:checked) .tt15p: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: Slab Serif Swash
Source: https://codefronts.com/navigation/css-tabs/slab-serif-swash/
Italic slab-serif labels on warm-cream stock. The active tab gets a deep-emerald swash underline — a curved SVG stroke beneath the word, like a calligrapher's flourish on letterpress vellum.
## HTML
```html
<div class="tt15">
<nav class="tt15n">
<input type="radio" name="tt15" id="tt15-r1" checked />
<label class="tt15b" for="tt15-r1">
Manifesto
<svg class="tt15s" viewBox="0 0 80 12" aria-hidden="true">
<path
d="M 2 8 Q 20 2 40 6 T 78 8"
fill="none"
stroke="#0f4f3d"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</label>
<input type="radio" name="tt15" id="tt15-r2" />
<label class="tt15b" for="tt15-r2">
Colophon
<svg class="tt15s" viewBox="0 0 80 12" aria-hidden="true">
<path
d="M 2 8 Q 20 2 40 6 T 78 8"
fill="none"
stroke="#0f4f3d"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</label>
<input type="radio" name="tt15" id="tt15-r3" />
<label class="tt15b" for="tt15-r3">
Errata
<svg class="tt15s" viewBox="0 0 80 12" aria-hidden="true">
<path
d="M 2 8 Q 20 2 40 6 T 78 8"
fill="none"
stroke="#0f4f3d"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</label>
</nav>
<div class="tt15p">The founding statement of intent.</div>
<div class="tt15p">The closing inscription, type & paper notes.</div>
<div class="tt15p">Corrections issued after the first printing.</div>
</div>
```
## CSS
```css
.tt15 {
min-height: 100vh;
background: #fdf4d6;
padding: 22px 22px 26px;
font-family: ui-serif, "Times New Roman", serif;
width: 100%;
}
.tt15n {
display: flex;
gap: 28px;
align-items: flex-end;
}
.tt15n input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tt15b {
position: relative;
border: 0;
background: transparent;
padding: 0 0 4px;
font: italic 700 18px/1 ui-serif, "Times New Roman", serif;
color: rgba(15, 79, 61, 0.55);
cursor: pointer;
transition: color 0.25s;
}
.tt15b:hover {
color: rgba(15, 79, 61, 0.9);
}
.tt15n input:checked + .tt15b {
color: #0f4f3d;
}
.tt15s {
position: absolute;
bottom: -8px;
left: 0;
right: 0;
width: 100%;
height: 12px;
opacity: 0;
stroke-dasharray: 100;
stroke-dashoffset: 100;
transition: opacity 0.2s, stroke-dashoffset 0.6s cubic-bezier(0.65, 0, 0.35, 1);
}
.tt15n input:checked + .tt15b .tt15s {
opacity: 1;
stroke-dashoffset: 0;
}
.tt15p {
display: none;
padding-top: 18px;
font: italic 12px/1.6 ui-serif, Georgia;
color: #3a3a42;
}
.tt15:has(#tt15-r1:checked) .tt15p:nth-of-type(1),
.tt15:has(#tt15-r2:checked) .tt15p:nth-of-type(2),
.tt15:has(#tt15-r3:checked) .tt15p:nth-of-type(3) {
display: block;
}
```How this works
The .tt15 demo sets italic slab-serif labels on a warm-cream #fdf4d6 stock in deep emerald #0f4f3d ink — the register of a private press manifesto or a McSweeney's colophon page. The active tab reveals an SVG swash underneath the word: a hand-drawn quadratic Bezier path M 2 8 Q 20 2 40 6 T 78 8 that sweeps beneath the letters, drawing itself on rather than fading, like a calligrapher's flourish finishing after the letterforms.
The drawing effect uses the stroke-dasharray plus stroke-dashoffset pair. The swash SVG starts at stroke-dasharray:100; stroke-dashoffset:100, so the entire stroke is offset out of view. On input:checked + .tt15b .tt15s, opacity moves to 1 over 0.2s and dashoffset transitions to 0 over 0.6s on cubic-bezier(0.65, 0, 0.35, 1). Because the two transitions run at different durations, the stroke appears to ink itself in from left to right rather than materialising all at once — the effect that gives the flourish its calligraphic feel.
Label typography is italic 700 18px/1 ui-serif, Times New Roman, serif. Inactive labels sit at rgba(15, 79, 61, 0.55) and check state moves to solid #0f4f3d — the same emerald as the swash stroke, so the flourish reads as an extension of the letterform rather than a separate underline. Panels route through :has() and nth-of-type. Copy-paste, semantic radios, WCAG 2.1 focusable, framework-agnostic, no dependencies, SSR-safe.
Make it yours
- Change the emerald #0f4f3d to any letterpress ink — burgundy #5c1a2b, oxblood #3d0f0f. Update the label colour, the rgba(15, 79, 61, 0.55) inactive tone, and the SVG stroke attribute in all three swashes together, since the stroke is inline.
- Redraw the swash by editing the SVG path M 2 8 Q 20 2 40 6 T 78 8. Higher control points give a taller flourish; more segments give a Spencerian tail. Keep the viewBox at 0 0 80 12 so the aspect scales with the label width via width:100%.
- The stroke-dasharray:100 assumes the swash path is roughly 80-100 units long. If you draw a longer flourish, bump both the stroke-dasharray and initial stroke-dashoffset to match — otherwise the ink-on transition ends early or leaves a gap.
- The type size is 18px italic slab — larger than most tab labels. That is deliberate for the display-typography register. Drop to 15px only if space is tight, but confirm the swash still reads as a flourish rather than a underline artifact.
- The gap:28px between tabs is what gives each label breathing room for its swash. Reduce below 18px and adjacent swashes will visually collide when two tabs sit next to each other with hover feedback from the passive one.
Gotchas — read before shipping
- The swash SVG uses inline stroke="#0f4f3d" attributes, not CSS. If you set stroke via a stylesheet the inline attribute wins and your override is ignored. Either edit all three inline strokes or drop them and move to CSS.
- The stroke draws via stroke-dashoffset. That property must be animated in a browser that supports SMIL-free CSS SVG animation — Firefox needed the fix a decade ago, so anything current is fine, but IE11 will not render the draw effect at all.
- The .tt15s is absolutely positioned at bottom:-8px. If you increase the label padding-bottom, the swash detaches from the letterform and reads as a separator line. Keep the 4px padding-bottom and -8px offset paired.
- stroke-dasharray:100 is a magic number tied to the path length. If a linter or formatter converts your path into a shorter cubic representation, the dasharray will over-run and the tail of the swash will never appear. Lock the path string.
- The :has() panel routing is the standard cluster fallback story — Safari 15.4, Chrome 105, Firefox 121. Provide a @supports not (selector(:has(*))) block that shows all three .tt15p captions if you serve older Firefox ESR channels.
- The italic slab-serif runs off the system ui-serif stack falling back to Times New Roman. On systems where ui-serif resolves to something without an italic cut, the label degrades to a synthesised oblique. Provide a webfont or a stricter fallback if that matters.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 105+ | 15.4+ | 121+ | 105+ |
:has() gates panel switching. The swash draw effect itself has been supported across all major browsers for years — only the caption routing needs a fallback for older Firefox ESR.