Pure TailwindMIT licensed

Minimal Checkmark Radio Button in Tailwind

Radical restraint: a typography-first list where the only ornament is a checkmark that draws itself. The tick is an SVG polyline animated with [stroke-dashoffset:28] → group-has-checked:[stroke-dashoffset:0], and each option now previews itself — the grotesk, serif and mono choices are set in the faces they name.

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="trb-13 w-full min-h-svh grid place-items-center bg-trb13-bg px-6 py-14 font-trb13-sans text-trb13-ink" aria-label="Minimal checkmark radio buttons demo">
  <fieldset class="w-full max-w-[22rem]">
    <legend class="text-xs font-semibold tracking-[0.18em] text-trb13-mut uppercase">Typeface</legend>
    <div class="mt-3.5 grid">
      <label class="group relative flex min-h-16 cursor-pointer items-center gap-3.5 border-b border-trb13-line has-focus-visible:outline-3 has-focus-visible:outline-offset-3 has-focus-visible:outline-trb13-ink"><input type="radio" name="trb-13" value="grotesk" class="sr-only" checked><svg class="size-5 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline class="[stroke-dasharray:28] [stroke-dashoffset:28] transition-[stroke-dashoffset] duration-350 ease-trb13-draw group-has-checked:[stroke-dashoffset:0] motion-reduce:transition-none" points="4.5 12.5 9.5 17.5 19.5 6.5"/></svg><span class="font-trb13-sans text-[21px] font-medium tracking-[-0.02em] text-trb13-ink/60 transition-colors duration-300 group-hover:text-trb13-ink/80 group-has-checked:text-trb13-ink">Neue Grotesk</span><span class="ml-auto font-trb13-mono text-[11px] text-trb13-mut">Sans</span></label>
      <label class="group relative flex min-h-16 cursor-pointer items-center gap-3.5 border-b border-trb13-line has-focus-visible:outline-3 has-focus-visible:outline-offset-3 has-focus-visible:outline-trb13-ink"><input type="radio" name="trb-13" value="serif" class="sr-only"><svg class="size-5 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline class="[stroke-dasharray:28] [stroke-dashoffset:28] transition-[stroke-dashoffset] duration-350 ease-trb13-draw group-has-checked:[stroke-dashoffset:0] motion-reduce:transition-none" points="4.5 12.5 9.5 17.5 19.5 6.5"/></svg><span class="font-trb13-serif text-[26px] leading-none text-trb13-ink/60 italic transition-colors duration-300 group-hover:text-trb13-ink/80 group-has-checked:text-trb13-ink">Editorial Serif</span><span class="ml-auto font-trb13-mono text-[11px] text-trb13-mut">Serif</span></label>
      <label class="group relative flex min-h-16 cursor-pointer items-center gap-3.5 border-b border-trb13-line has-focus-visible:outline-3 has-focus-visible:outline-offset-3 has-focus-visible:outline-trb13-ink"><input type="radio" name="trb-13" value="mono" class="sr-only"><svg class="size-5 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline class="[stroke-dasharray:28] [stroke-dashoffset:28] transition-[stroke-dashoffset] duration-350 ease-trb13-draw group-has-checked:[stroke-dashoffset:0] motion-reduce:transition-none" points="4.5 12.5 9.5 17.5 19.5 6.5"/></svg><span class="font-trb13-mono text-lg tracking-[-0.02em] text-trb13-ink/60 transition-colors duration-300 group-hover:text-trb13-ink/80 group-has-checked:text-trb13-ink">Studio Mono</span><span class="ml-auto font-trb13-mono text-[11px] text-trb13-mut">Mono</span></label>
    </div>
  </fieldset>
</section>
@import "tailwindcss";

@theme {
  --color-trb13-bg: #fcfcfa;
  --color-trb13-bg: oklch(0.99 0.003 100);
  --color-trb13-ink: #191919;
  --color-trb13-ink: oklch(0.21 0 0);
  --color-trb13-mut: #6b6b66;
  --color-trb13-mut: oklch(0.52 0.008 100);
  --color-trb13-line: #e6e6e0;
  --color-trb13-line: oklch(0.92 0.006 100);
  --ease-trb13-draw: cubic-bezier(0.22, 1, 0.36, 1);
  --font-trb13-sans: 'Geist', system-ui, sans-serif;
  --font-trb13-serif: 'Instrument Serif', Georgia, serif;
  --font-trb13-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. */

.trb-13 {
  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-trb13-bg: #fcfcfa;
  --color-trb13-bg: oklch(0.99 0.003 100);
  --color-trb13-ink: #191919;
  --color-trb13-ink: oklch(0.21 0 0);
  --color-trb13-mut: #6b6b66;
  --color-trb13-mut: oklch(0.52 0.008 100);
  --color-trb13-line: #e6e6e0;
  --color-trb13-line: oklch(0.92 0.006 100);
  --ease-trb13-draw: cubic-bezier(0.22, 1, 0.36, 1);
  --font-trb13-sans: 'Geist', system-ui, sans-serif;
  --font-trb13-serif: 'Instrument Serif', Georgia, serif;
  --font-trb13-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. */

.trb-13 {
  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=Geist:wght@400..700&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap">

What changed in the port

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

  • The SVG dash rules became arbitrary properties on the polyline; the state variant stacks on them exactly like a utility.
  • rgba(25,25,25,.55) became the opacity modifier text-trb13-ink/60 (nudged up to clear 4.5:1).
  • UI upgrade from the CSS original: each option is now set in the face it names (Geist, Instrument Serif italic, JetBrains Mono), with a small classification tag — the picker is its own specimen sheet.
  • UI upgrade: the checked row changes ink instead of weight, so text never reflows; the legend rose from #9a9a94 (2.7:1) to 5.4:1, and rows grew to 64px.
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: Minimal Checkmark Radio Button in Tailwind
Source: https://codefronts.com/components/tailwind-css-radio-buttons/minimal-checkmark-radio-button-in-tailwind/

Radical restraint: a typography-first list where the only ornament is a checkmark that draws itself. The tick is an SVG polyline animated with [stroke-dashoffset:28] → group-has-checked:[stroke-dashoffset:0], and each option now previews itself — the grotesk, serif and mono choices are set in the faces they name.
## HTML
```html
<section class="trb-13 w-full min-h-svh grid place-items-center bg-trb13-bg px-6 py-14 font-trb13-sans text-trb13-ink" aria-label="Minimal checkmark radio buttons demo">
  <fieldset class="w-full max-w-[22rem]">
    <legend class="text-xs font-semibold tracking-[0.18em] text-trb13-mut uppercase">Typeface</legend>
    <div class="mt-3.5 grid">
      <label class="group relative flex min-h-16 cursor-pointer items-center gap-3.5 border-b border-trb13-line has-focus-visible:outline-3 has-focus-visible:outline-offset-3 has-focus-visible:outline-trb13-ink"><input type="radio" name="trb-13" value="grotesk" class="sr-only" checked><svg class="size-5 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline class="[stroke-dasharray:28] [stroke-dashoffset:28] transition-[stroke-dashoffset] duration-350 ease-trb13-draw group-has-checked:[stroke-dashoffset:0] motion-reduce:transition-none" points="4.5 12.5 9.5 17.5 19.5 6.5"/></svg><span class="font-trb13-sans text-[21px] font-medium tracking-[-0.02em] text-trb13-ink/60 transition-colors duration-300 group-hover:text-trb13-ink/80 group-has-checked:text-trb13-ink">Neue Grotesk</span><span class="ml-auto font-trb13-mono text-[11px] text-trb13-mut">Sans</span></label>
      <label class="group relative flex min-h-16 cursor-pointer items-center gap-3.5 border-b border-trb13-line has-focus-visible:outline-3 has-focus-visible:outline-offset-3 has-focus-visible:outline-trb13-ink"><input type="radio" name="trb-13" value="serif" class="sr-only"><svg class="size-5 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline class="[stroke-dasharray:28] [stroke-dashoffset:28] transition-[stroke-dashoffset] duration-350 ease-trb13-draw group-has-checked:[stroke-dashoffset:0] motion-reduce:transition-none" points="4.5 12.5 9.5 17.5 19.5 6.5"/></svg><span class="font-trb13-serif text-[26px] leading-none text-trb13-ink/60 italic transition-colors duration-300 group-hover:text-trb13-ink/80 group-has-checked:text-trb13-ink">Editorial Serif</span><span class="ml-auto font-trb13-mono text-[11px] text-trb13-mut">Serif</span></label>
      <label class="group relative flex min-h-16 cursor-pointer items-center gap-3.5 border-b border-trb13-line has-focus-visible:outline-3 has-focus-visible:outline-offset-3 has-focus-visible:outline-trb13-ink"><input type="radio" name="trb-13" value="mono" class="sr-only"><svg class="size-5 flex-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline class="[stroke-dasharray:28] [stroke-dashoffset:28] transition-[stroke-dashoffset] duration-350 ease-trb13-draw group-has-checked:[stroke-dashoffset:0] motion-reduce:transition-none" points="4.5 12.5 9.5 17.5 19.5 6.5"/></svg><span class="font-trb13-mono text-lg tracking-[-0.02em] text-trb13-ink/60 transition-colors duration-300 group-hover:text-trb13-ink/80 group-has-checked:text-trb13-ink">Studio Mono</span><span class="ml-auto font-trb13-mono text-[11px] text-trb13-mut">Mono</span></label>
    </div>
  </fieldset>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-trb13-bg: #fcfcfa;
  --color-trb13-bg: oklch(0.99 0.003 100);
  --color-trb13-ink: #191919;
  --color-trb13-ink: oklch(0.21 0 0);
  --color-trb13-mut: #6b6b66;
  --color-trb13-mut: oklch(0.52 0.008 100);
  --color-trb13-line: #e6e6e0;
  --color-trb13-line: oklch(0.92 0.006 100);
  --ease-trb13-draw: cubic-bezier(0.22, 1, 0.36, 1);
  --font-trb13-sans: 'Geist', system-ui, sans-serif;
  --font-trb13-serif: 'Instrument Serif', Georgia, serif;
  --font-trb13-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. */

.trb-13 {
  width: 100%;
  min-height: 100svh;
}
```

How this works

Arbitrary properties cover SVG. Tailwind has stroke-* colour and width utilities but none for dashes; [stroke-dashoffset:28] writes the declaration directly, and variants stack on it like any utility.

No box, no circle, still accessible. Underneath is a native radio group in a fieldset with an sr-only legend echo, arrow keys and a visible 3px focus outline on the row.

Make it yours

  • Draw speed: duration-350 on the polyline.
  • Resting ink: /60 — keep it at or above 4.5:1 on your background.
  • Swap the specimen fonts via the three --font-trb13-* tokens.

Gotchas — read before shipping

  • Set the dasharray to the true path length (getTotalLength() in devtools) or the draw stutters.
  • Weight changes reflow text; this demo changes ink, not weight, so rows never shift.
  • A focus style must exist even in minimal designs.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

SVG dash animation is universal; :has() drives the draw and the ink change.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…