32 Tailwind CSS Tabs
24 / 32
Pure TailwindMIT licensed

Outlined Tabs with Corner Markers in Tailwind CSS

Hairline-framed tabs on warm white. The chosen frame inks a 4px navy top edge and four dots pop onto its corners a beat later, like crop marks. The 4px top border is reserved transparent from the start, so selecting a tab never moves the row by a pixel.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="ttb-24 group/tabs grid min-h-svh w-full place-items-center bg-ttb24-bg px-5 py-14 font-ttb24-serif text-ttb24-navy" aria-label="Outlined tabs with corner markers demo">
  <div class="grid w-full max-w-lg justify-items-center gap-7">
    <div class="w-full">
      <div role="radiogroup" aria-label="Stage" class="flex flex-wrap gap-3.5">
        <label class="group/tab relative flex min-h-12 flex-1 cursor-pointer items-center justify-center gap-2 border border-t-4 border-ttb24-navy/40 border-t-transparent px-5 text-[15px] font-semibold tracking-[0.03em] text-ttb24-mut transition-[color,border-color] duration-300 hover:text-ttb24-navy has-checked:border-ttb24-navy has-checked:border-t-ttb24-navy has-checked:text-ttb24-navy has-focus-visible:outline-2 has-focus-visible:outline-offset-5 has-focus-visible:outline-ttb24-navy"><input class="sr-only" type="radio" name="ttb24" id="ttb24-r1" checked>Drafts <span class="font-ttb24-mono text-[11px] font-medium tabular-nums">12</span><i aria-hidden="true" class="absolute -top-1 -left-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -top-1 -right-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -bottom-1 -left-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -right-1 -bottom-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i></label>
        <label class="group/tab relative flex min-h-12 flex-1 cursor-pointer items-center justify-center gap-2 border border-t-4 border-ttb24-navy/40 border-t-transparent px-5 text-[15px] font-semibold tracking-[0.03em] text-ttb24-mut transition-[color,border-color] duration-300 hover:text-ttb24-navy has-checked:border-ttb24-navy has-checked:border-t-ttb24-navy has-checked:text-ttb24-navy has-focus-visible:outline-2 has-focus-visible:outline-offset-5 has-focus-visible:outline-ttb24-navy"><input class="sr-only" type="radio" name="ttb24" id="ttb24-r2">Proofs <span class="font-ttb24-mono text-[11px] font-medium tabular-nums">4</span><i aria-hidden="true" class="absolute -top-1 -left-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -top-1 -right-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -bottom-1 -left-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -right-1 -bottom-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i></label>
        <label class="group/tab relative flex min-h-12 flex-1 cursor-pointer items-center justify-center gap-2 border border-t-4 border-ttb24-navy/40 border-t-transparent px-5 text-[15px] font-semibold tracking-[0.03em] text-ttb24-mut transition-[color,border-color] duration-300 hover:text-ttb24-navy has-checked:border-ttb24-navy has-checked:border-t-ttb24-navy has-checked:text-ttb24-navy has-focus-visible:outline-2 has-focus-visible:outline-offset-5 has-focus-visible:outline-ttb24-navy"><input class="sr-only" type="radio" name="ttb24" id="ttb24-r3">Final <span class="font-ttb24-mono text-[11px] font-medium tabular-nums">31</span><i aria-hidden="true" class="absolute -top-1 -left-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -top-1 -right-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -bottom-1 -left-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -right-1 -bottom-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i></label>
      </div>
      <div class="pt-5 text-[16px] text-ttb24-ash italic">
        <p class="hidden group-has-[#ttb24-r1:checked]/tabs:block">Working files in progress.</p>
        <p class="hidden group-has-[#ttb24-r2:checked]/tabs:block">Galleys ready for review.</p>
        <p class="hidden group-has-[#ttb24-r3:checked]/tabs:block">Approved and locked.</p>
      </div>
    </div>
    <p aria-hidden="true" class="border border-ttb24-navy/30 px-3.5 py-2 font-ttb24-mono text-[11.5px] text-ttb24-mut">border-t-transparent reserved · corner dots delay-100</p>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-ttb24-bg: #f5f1ea;
  --color-ttb24-bg: oklch(0.959 0.010 81.8);
  --color-ttb24-navy: #1a2842;
  --color-ttb24-navy: oklch(0.278 0.052 262.0);
  --color-ttb24-mut: #5a6478;
  --color-ttb24-mut: oklch(0.502 0.034 264.2);
  --color-ttb24-ash: #3a3a42;
  --color-ttb24-ash: oklch(0.352 0.014 285.7);
  --font-ttb24-serif: 'Newsreader', Georgia, serif;
  --font-ttb24-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
   stage is sized before the browser build compiles. */

.ttb-24 {
  width: 100%;
  min-height: 100svh;
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-ttb24-bg: #f5f1ea;
  --color-ttb24-bg: oklch(0.959 0.010 81.8);
  --color-ttb24-navy: #1a2842;
  --color-ttb24-navy: oklch(0.278 0.052 262.0);
  --color-ttb24-mut: #5a6478;
  --color-ttb24-mut: oklch(0.502 0.034 264.2);
  --color-ttb24-ash: #3a3a42;
  --color-ttb24-ash: oklch(0.352 0.014 285.7);
  --font-ttb24-serif: 'Newsreader', Georgia, serif;
  --font-ttb24-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
   stage is sized before the browser build compiles. */

.ttb-24 {
  width: 100%;
  min-height: 100svh;
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400..700;1,6..72,400..700&family=JetBrains+Mono:wght@400;500&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • border-top:4px solid transparent → border-t-4 border-t-transparent; the inked state is has-checked:border-t-ttb24-navy.
  • The four .tt12tk-N classes (one per corner, each with its own transform:scale(0)) became four <i> elements sharing one class list apart from position.
  • UI upgrade from the CSS original: per-tab counts in tabular mono, and the 0.5px hairline (inconsistent at 1x) became 1px at 40% ink.
  • UI upgrade: resting labels went from 50% navy (≈4.2:1) to a 5.7:1 token; tabs wrap on phones.
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 demo 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: Outlined Tabs with Corner Markers in Tailwind CSS
Source: https://codefronts.com/navigation/tailwind-css-tabs/outlined-tabs-corner-markers-tailwind-css/

Hairline-framed tabs on warm white. The chosen frame inks a 4px navy top edge and four dots pop onto its corners a beat later, like crop marks. The 4px top border is reserved transparent from the start, so selecting a tab never moves the row by a pixel.
## HTML
```html
<section class="ttb-24 group/tabs grid min-h-svh w-full place-items-center bg-ttb24-bg px-5 py-14 font-ttb24-serif text-ttb24-navy" aria-label="Outlined tabs with corner markers demo">
  <div class="grid w-full max-w-lg justify-items-center gap-7">
    <div class="w-full">
      <div role="radiogroup" aria-label="Stage" class="flex flex-wrap gap-3.5">
        <label class="group/tab relative flex min-h-12 flex-1 cursor-pointer items-center justify-center gap-2 border border-t-4 border-ttb24-navy/40 border-t-transparent px-5 text-[15px] font-semibold tracking-[0.03em] text-ttb24-mut transition-[color,border-color] duration-300 hover:text-ttb24-navy has-checked:border-ttb24-navy has-checked:border-t-ttb24-navy has-checked:text-ttb24-navy has-focus-visible:outline-2 has-focus-visible:outline-offset-5 has-focus-visible:outline-ttb24-navy"><input class="sr-only" type="radio" name="ttb24" id="ttb24-r1" checked>Drafts <span class="font-ttb24-mono text-[11px] font-medium tabular-nums">12</span><i aria-hidden="true" class="absolute -top-1 -left-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -top-1 -right-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -bottom-1 -left-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -right-1 -bottom-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i></label>
        <label class="group/tab relative flex min-h-12 flex-1 cursor-pointer items-center justify-center gap-2 border border-t-4 border-ttb24-navy/40 border-t-transparent px-5 text-[15px] font-semibold tracking-[0.03em] text-ttb24-mut transition-[color,border-color] duration-300 hover:text-ttb24-navy has-checked:border-ttb24-navy has-checked:border-t-ttb24-navy has-checked:text-ttb24-navy has-focus-visible:outline-2 has-focus-visible:outline-offset-5 has-focus-visible:outline-ttb24-navy"><input class="sr-only" type="radio" name="ttb24" id="ttb24-r2">Proofs <span class="font-ttb24-mono text-[11px] font-medium tabular-nums">4</span><i aria-hidden="true" class="absolute -top-1 -left-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -top-1 -right-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -bottom-1 -left-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -right-1 -bottom-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i></label>
        <label class="group/tab relative flex min-h-12 flex-1 cursor-pointer items-center justify-center gap-2 border border-t-4 border-ttb24-navy/40 border-t-transparent px-5 text-[15px] font-semibold tracking-[0.03em] text-ttb24-mut transition-[color,border-color] duration-300 hover:text-ttb24-navy has-checked:border-ttb24-navy has-checked:border-t-ttb24-navy has-checked:text-ttb24-navy has-focus-visible:outline-2 has-focus-visible:outline-offset-5 has-focus-visible:outline-ttb24-navy"><input class="sr-only" type="radio" name="ttb24" id="ttb24-r3">Final <span class="font-ttb24-mono text-[11px] font-medium tabular-nums">31</span><i aria-hidden="true" class="absolute -top-1 -left-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -top-1 -right-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -bottom-1 -left-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i><i aria-hidden="true" class="absolute -right-1 -bottom-1 size-[7px] scale-0 rounded-full bg-ttb24-navy transition-transform delay-100 duration-350 ease-[cubic-bezier(0.34,1.56,0.64,1)] group-has-checked/tab:scale-100"></i></label>
      </div>
      <div class="pt-5 text-[16px] text-ttb24-ash italic">
        <p class="hidden group-has-[#ttb24-r1:checked]/tabs:block">Working files in progress.</p>
        <p class="hidden group-has-[#ttb24-r2:checked]/tabs:block">Galleys ready for review.</p>
        <p class="hidden group-has-[#ttb24-r3:checked]/tabs:block">Approved and locked.</p>
      </div>
    </div>
    <p aria-hidden="true" class="border border-ttb24-navy/30 px-3.5 py-2 font-ttb24-mono text-[11.5px] text-ttb24-mut">border-t-transparent reserved · corner dots delay-100</p>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-ttb24-bg: #f5f1ea;
  --color-ttb24-bg: oklch(0.959 0.010 81.8);
  --color-ttb24-navy: #1a2842;
  --color-ttb24-navy: oklch(0.278 0.052 262.0);
  --color-ttb24-mut: #5a6478;
  --color-ttb24-mut: oklch(0.502 0.034 264.2);
  --color-ttb24-ash: #3a3a42;
  --color-ttb24-ash: oklch(0.352 0.014 285.7);
  --font-ttb24-serif: 'Newsreader', Georgia, serif;
  --font-ttb24-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
   stage is sized before the browser build compiles. */

.ttb-24 {
  width: 100%;
  min-height: 100svh;
}
```

How this works

Reserve the weight. A border that appears on selection would push content down by 4px. Declaring it transparent up front makes selection a colour change only:

<label class="border border-t-4 border-ttb24-navy/50 border-t-transparent
  has-checked:border-ttb24-navy has-checked:border-t-ttb24-navy">

The corner dots sit at -top-1 -left-1 etc. (half their size outside each corner) and pop with an overshoot ease after a 100ms delay, so the frame inks first and the marks follow.

Make it yours

  • Burgundy or forest ink: swap --color-ttb24-navy.
  • Square crop marks: drop rounded-full from the dots.
  • Hide counts by deleting the badge spans.

Gotchas — read before shipping

  • 0.5px hairlines render unevenly at 1x; the port uses 1px at 50% ink instead.
  • Dots overlap neighbours if the gap is under 8px.
  • Counts are part of each radio's name — keep them meaningful.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

:has() for panels.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…