Tailwind v4MIT licensed

Tailwind Stroke Draw Button with pathLength Normalization

Five silhouettes — hexagon, diamond, shield, octagon, ring — animated by one class string, character for character, apart from colour. Because every path carries pathLength="1", [stroke-dasharray:1] [stroke-dashoffset:1] is a complete outline on all of them, which means the string is genuinely reusable: extract it into an @utility and it animates any shape you point it at.

Live Demo
Plain CSSTailwind v4
Try it

Nucleo: 44,000+ SVG icons in five families · All Icons Bundle $149

Nucleo · Affiliate link

Get the Nucleo bundle

The code

<section class="svgb-11 relative block w-full min-h-svh overflow-hidden bg-svgb11-bg text-svgb11-fg font-svgb11-sans">
  <input class="peer absolute opacity-0 pointer-events-none" type="checkbox" id="svgb-11-theme">
  <label class="absolute z-[5] top-4 right-4 grid place-items-center size-11 border border-current/24 bg-current/8 cursor-pointer transition-[background-color,transform] duration-150 hover:bg-current/16 active:scale-95 peer-focus-visible:outline-2 peer-focus-visible:outline-current peer-focus-visible:outline-offset-[3px] peer-checked:[&_svg]:rotate-180" for="svgb-11-theme">
    <svg class="size-[18px] transition-transform duration-[400ms] ease-[cubic-bezier(0.2,0.7,0.3,1)]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><circle cx="12" cy="12" r="8.5"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor" stroke="none"/></svg>
    <span class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">Switch colour scheme</span>
  </label>
  <div class="relative min-h-svh flex flex-col items-center justify-center gap-[clamp(14px,3vh,30px)] p-[clamp(22px,5vw,70px)] text-center">
    <p class="m-0 font-svgb11-mono text-[11px] tracking-[0.3em] uppercase text-svgb11-alt animate-svgb11-blink motion-reduce:animate-none">Insert coin &middot; 1 credit</p>
    <h1 class="m-0 text-[clamp(40px,9vw,96px)] font-bold tracking-[-0.02em] uppercase leading-[0.95] [text-shadow:var(--svgb-11-logo-sh)] [&_span]:text-svgb11-stroke">Vector<span>Runner</span></h1>
    <div class="flex flex-wrap justify-center items-center gap-[clamp(12px,2.4vw,26px)]">
      <button class="group relative grid place-items-center border-0 p-0 bg-transparent text-[15px] font-semibold tracking-[0.14em] uppercase cursor-pointer focus-visible:outline-dashed focus-visible:outline-1 focus-visible:outline-svgb11-alt focus-visible:outline-offset-[6px] w-50 h-24" type="button">
        <svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 200 96" aria-hidden="true">
          <path class="fill-none stroke-svgb11-rest [stroke-width:1.6]" d="M40 4H160L196 48L160 92H40L4 48Z" pathLength="1"/>
          <path class="fill-none [stroke-width:2.4] [stroke-linecap:butt] [stroke-linejoin:miter] [stroke-dasharray:1] [stroke-dashoffset:1] transition-[stroke-dashoffset] duration-[620ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] motion-reduce:transition-none motion-reduce:[stroke-dasharray:none] motion-reduce:[stroke-dashoffset:0] motion-reduce:opacity-0 motion-reduce:group-hover:opacity-100 motion-reduce:group-focus-visible:opacity-100 stroke-svgb11-stroke [filter:var(--svgb-11-glow)]" d="M40 4H160L196 48L160 92H40L4 48Z" pathLength="1"/>
        </svg>
        <span class="relative z-[1] transition-[color,text-shadow] duration-[260ms] motion-reduce:transition-none group-hover:text-svgb11-stroke group-hover:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-stroke)_70%,transparent)] group-focus-visible:text-svgb11-stroke group-focus-visible:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-stroke)_70%,transparent)]">Start</span>
      </button>
      <button class="group relative grid place-items-center border-0 p-0 bg-transparent text-[15px] font-semibold tracking-[0.14em] uppercase cursor-pointer focus-visible:outline-dashed focus-visible:outline-1 focus-visible:outline-svgb11-alt focus-visible:outline-offset-[6px] w-40 h-24" type="button">
        <svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 160 96" aria-hidden="true">
          <path class="fill-none stroke-svgb11-rest [stroke-width:1.6]" d="M80 4L156 48L80 92L4 48Z" pathLength="1"/>
          <path class="fill-none [stroke-width:2.4] [stroke-linecap:butt] [stroke-linejoin:miter] [stroke-dasharray:1] [stroke-dashoffset:1] transition-[stroke-dashoffset] duration-[620ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] motion-reduce:transition-none motion-reduce:[stroke-dasharray:none] motion-reduce:[stroke-dashoffset:0] motion-reduce:opacity-0 motion-reduce:group-hover:opacity-100 motion-reduce:group-focus-visible:opacity-100 stroke-svgb11-alt [filter:var(--svgb-11-glow-alt)]" d="M80 4L156 48L80 92L4 48Z" pathLength="1"/>
        </svg>
        <span class="relative z-[1] transition-[color,text-shadow] duration-[260ms] motion-reduce:transition-none group-hover:text-svgb11-alt group-hover:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-alt)_70%,transparent)] group-focus-visible:text-svgb11-alt group-focus-visible:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-alt)_70%,transparent)]">Co-op</span>
      </button>
      <button class="group relative grid place-items-center border-0 p-0 bg-transparent text-[15px] font-semibold tracking-[0.14em] uppercase cursor-pointer focus-visible:outline-dashed focus-visible:outline-1 focus-visible:outline-svgb11-alt focus-visible:outline-offset-[6px] w-40 h-26" type="button">
        <svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 160 104" aria-hidden="true">
          <path class="fill-none stroke-svgb11-rest [stroke-width:1.6]" d="M80 4L152 24V58C152 82 118 96 80 100C42 96 8 82 8 58V24Z" pathLength="1"/>
          <path class="fill-none [stroke-width:2.4] [stroke-linecap:butt] [stroke-linejoin:miter] [stroke-dasharray:1] [stroke-dashoffset:1] transition-[stroke-dashoffset] duration-[620ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] motion-reduce:transition-none motion-reduce:[stroke-dasharray:none] motion-reduce:[stroke-dashoffset:0] motion-reduce:opacity-0 motion-reduce:group-hover:opacity-100 motion-reduce:group-focus-visible:opacity-100 stroke-svgb11-stroke [filter:var(--svgb-11-glow)]" d="M80 4L152 24V58C152 82 118 96 80 100C42 96 8 82 8 58V24Z" pathLength="1"/>
        </svg>
        <span class="relative z-[1] transition-[color,text-shadow] duration-[260ms] motion-reduce:transition-none group-hover:text-svgb11-stroke group-hover:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-stroke)_70%,transparent)] group-focus-visible:text-svgb11-stroke group-focus-visible:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-stroke)_70%,transparent)]">Armory</span>
      </button>
      <button class="group relative grid place-items-center border-0 p-0 bg-transparent text-[15px] font-semibold tracking-[0.14em] uppercase cursor-pointer focus-visible:outline-dashed focus-visible:outline-1 focus-visible:outline-svgb11-alt focus-visible:outline-offset-[6px] w-50 h-24" type="button">
        <svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 200 96" aria-hidden="true">
          <path class="fill-none stroke-svgb11-rest [stroke-width:1.6]" d="M28 4H172L196 28V68L172 92H28L4 68V28Z" pathLength="1"/>
          <path class="fill-none [stroke-width:2.4] [stroke-linecap:butt] [stroke-linejoin:miter] [stroke-dasharray:1] [stroke-dashoffset:1] transition-[stroke-dashoffset] duration-[620ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] motion-reduce:transition-none motion-reduce:[stroke-dasharray:none] motion-reduce:[stroke-dashoffset:0] motion-reduce:opacity-0 motion-reduce:group-hover:opacity-100 motion-reduce:group-focus-visible:opacity-100 stroke-svgb11-stroke [filter:var(--svgb-11-glow)]" d="M28 4H172L196 28V68L172 92H28L4 68V28Z" pathLength="1"/>
        </svg>
        <span class="relative z-[1] transition-[color,text-shadow] duration-[260ms] motion-reduce:transition-none group-hover:text-svgb11-stroke group-hover:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-stroke)_70%,transparent)] group-focus-visible:text-svgb11-stroke group-focus-visible:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-stroke)_70%,transparent)]">Options</span>
      </button>
      <button class="group relative grid place-items-center border-0 p-0 bg-transparent text-[15px] font-semibold tracking-[0.14em] uppercase cursor-pointer focus-visible:outline-dashed focus-visible:outline-1 focus-visible:outline-svgb11-alt focus-visible:outline-offset-[6px] w-24 h-24" type="button" aria-label="Quit to attract mode">
        <svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 96 96" aria-hidden="true">
          <circle class="fill-none stroke-svgb11-rest [stroke-width:1.6]" cx="48" cy="48" r="44" pathLength="1"/>
          <circle class="fill-none [stroke-width:2.4] [stroke-linecap:butt] [stroke-linejoin:miter] [stroke-dasharray:1] [stroke-dashoffset:1] transition-[stroke-dashoffset] duration-[620ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] motion-reduce:transition-none motion-reduce:[stroke-dasharray:none] motion-reduce:[stroke-dashoffset:0] motion-reduce:opacity-0 motion-reduce:group-hover:opacity-100 motion-reduce:group-focus-visible:opacity-100 stroke-svgb11-alt [filter:var(--svgb-11-glow-alt)]" cx="48" cy="48" r="44" pathLength="1"/>
        </svg>
        <span class="relative z-[1] text-[26px] tracking-normal transition-[color,text-shadow] duration-[260ms] motion-reduce:transition-none group-hover:text-svgb11-alt group-focus-visible:text-svgb11-alt">&times;</span>
      </button>
    </div>
    <div class="grid grid-cols-[repeat(4,auto)] gap-x-3.5 gap-y-1 px-[18px] py-3 border border-svgb11-rest font-svgb11-mono text-[10.5px] tracking-[0.1em] uppercase">
      <span class="text-svgb11-muted">dasharray</span><span class="text-svgb11-stroke">1</span>
      <span class="text-svgb11-muted">dashoffset</span><span class="text-svgb11-stroke">1 &rarr; 0</span>
      <span class="text-svgb11-muted">shapes</span><span class="text-svgb11-stroke">5</span>
      <span class="text-svgb11-muted">constants</span><span class="text-svgb11-stroke">0</span>
    </div>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-svgb11-bg: #06070d;
  --color-svgb11-bg: oklch(0.145 0.02 265);
  --color-svgb11-fg: #e8f6ff;
  --color-svgb11-fg: oklch(0.96 0.02 220);
  --color-svgb11-muted: #7d90a8;
  --color-svgb11-muted: oklch(0.645 0.035 250);
  --color-svgb11-rest: rgba(232, 246, 255, 0.16);
  --color-svgb11-rest: oklch(0.96 0.02 220 / 0.16);
  --color-svgb11-stroke: #22f0ff;
  --color-svgb11-stroke: oklch(0.88 0.16 200);
  --color-svgb11-alt: #ff3ecb;
  --color-svgb11-alt: oklch(0.72 0.26 340);
  --font-svgb11-sans: 'Chakra Petch', system-ui, sans-serif;
  --font-svgb11-mono: 'JetBrains Mono', ui-monospace, monospace;
  --animate-svgb11-blink: svgb11-blink 1.8s steps(1, end) infinite;
}

@keyframes svgb11-blink {
  0%,
    60% {
    opacity: 1;
  }

  61%,
    100% {
    opacity: 0.25;
  }
}
/* Glows live in variables so the light scheme can switch them off without
   touching a single utility, and so both tones read the same class shape. */

.svgb-11 {
  --svgb-11-glow: drop-shadow(0 0 7px color-mix(in oklab, var(--color-svgb11-stroke) 60%, transparent));
  --svgb-11-glow-alt: drop-shadow(0 0 7px color-mix(in oklab, var(--color-svgb11-alt) 60%, transparent));
  --svgb-11-logo-sh: 0 0 22px color-mix(in oklab, var(--color-svgb11-stroke) 55%, transparent);
}
/* CRT scanlines and cabinet bloom: two decorative pseudo-element layers */

.svgb-11::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: repeating-linear-gradient(0deg, oklch(0 0 0 / 0.34) 0 1px, transparent 1px 3px);
  opacity: var(--svgb-11-scan, 0.55);
}

.svgb-11::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(60% 50% at 50% 0%, color-mix(in oklab, var(--color-svgb11-stroke) 14%, transparent), transparent 65%), radial-gradient(70% 60% at 50% 110%, color-mix(in oklab, var(--color-svgb11-alt) 14%, transparent), transparent 65%);
}

.svgb-11:has(#svgb-11-theme:checked) {
  --color-svgb11-bg: #eef2f6;
  --color-svgb11-bg: oklch(0.95 0.008 235);
  --color-svgb11-fg: #0b1220;
  --color-svgb11-fg: oklch(0.19 0.025 260);
  --color-svgb11-muted: #4f5f75;
  --color-svgb11-muted: oklch(0.49 0.03 255);
  --color-svgb11-rest: rgba(11, 18, 32, 0.2);
  --color-svgb11-rest: oklch(0.19 0.025 260 / 0.2);
  --color-svgb11-stroke: #006d8f;
  --color-svgb11-stroke: oklch(0.47 0.11 218);
  --color-svgb11-alt: #9c1173;
  --color-svgb11-alt: oklch(0.44 0.20 340);
  --svgb-11-glow: none;
  --svgb-11-glow-alt: none;
  --svgb-11-logo-sh: none;
  --svgb-11-scan: 0.18;
}

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-svgb11-bg: #06070d;
  --color-svgb11-bg: oklch(0.145 0.02 265);
  --color-svgb11-fg: #e8f6ff;
  --color-svgb11-fg: oklch(0.96 0.02 220);
  --color-svgb11-muted: #7d90a8;
  --color-svgb11-muted: oklch(0.645 0.035 250);
  --color-svgb11-rest: rgba(232, 246, 255, 0.16);
  --color-svgb11-rest: oklch(0.96 0.02 220 / 0.16);
  --color-svgb11-stroke: #22f0ff;
  --color-svgb11-stroke: oklch(0.88 0.16 200);
  --color-svgb11-alt: #ff3ecb;
  --color-svgb11-alt: oklch(0.72 0.26 340);
  --font-svgb11-sans: 'Chakra Petch', system-ui, sans-serif;
  --font-svgb11-mono: 'JetBrains Mono', ui-monospace, monospace;
  --animate-svgb11-blink: svgb11-blink 1.8s steps(1, end) infinite;
}

@keyframes svgb11-blink {
  0%,
    60% {
    opacity: 1;
  }

  61%,
    100% {
    opacity: 0.25;
  }
}
/* Glows live in variables so the light scheme can switch them off without
   touching a single utility, and so both tones read the same class shape. */

.svgb-11 {
  --svgb-11-glow: drop-shadow(0 0 7px color-mix(in oklab, var(--color-svgb11-stroke) 60%, transparent));
  --svgb-11-glow-alt: drop-shadow(0 0 7px color-mix(in oklab, var(--color-svgb11-alt) 60%, transparent));
  --svgb-11-logo-sh: 0 0 22px color-mix(in oklab, var(--color-svgb11-stroke) 55%, transparent);
}
/* CRT scanlines and cabinet bloom: two decorative pseudo-element layers */

.svgb-11::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: repeating-linear-gradient(0deg, oklch(0 0 0 / 0.34) 0 1px, transparent 1px 3px);
  opacity: var(--svgb-11-scan, 0.55);
}

.svgb-11::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(60% 50% at 50% 0%, color-mix(in oklab, var(--color-svgb11-stroke) 14%, transparent), transparent 65%), radial-gradient(70% 60% at 50% 110%, color-mix(in oklab, var(--color-svgb11-alt) 14%, transparent), transparent 65%);
}

.svgb-11:has(#svgb-11-theme:checked) {
  --color-svgb11-bg: #eef2f6;
  --color-svgb11-bg: oklch(0.95 0.008 235);
  --color-svgb11-fg: #0b1220;
  --color-svgb11-fg: oklch(0.19 0.025 260);
  --color-svgb11-muted: #4f5f75;
  --color-svgb11-muted: oklch(0.49 0.03 255);
  --color-svgb11-rest: rgba(11, 18, 32, 0.2);
  --color-svgb11-rest: oklch(0.19 0.025 260 / 0.2);
  --color-svgb11-stroke: #006d8f;
  --color-svgb11-stroke: oklch(0.47 0.11 218);
  --color-svgb11-alt: #9c1173;
  --color-svgb11-alt: oklch(0.44 0.20 340);
  --svgb-11-glow: none;
  --svgb-11-glow-alt: none;
  --svgb-11-logo-sh: none;
  --svgb-11-scan: 0.18;
}
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=Chakra+Petch:wght@400;600;700&family=JetBrains+Mono:wght@400;500&display=swap">

What changed in the port

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

  • The webfonts load as <link> tags in setup, not as an @import inside the stylesheet. Tailwind emits user @import rules after its own @layer blocks, and a late @import is invalid CSS — the browser drops it and the demo silently falls back to system fonts.
  • The five inks share one class string, character for character, apart from their colour. That is the demo: [stroke-dasharray:1] [stroke-dashoffset:1] is a complete outline on a hexagon, a shield and a circle alike, because every path carries pathLength=&quot;1&quot;.
  • Because there is no measured constant, the utility string is genuinely reusable — extract it into an @utility or a component variant and it will animate any silhouette you point it at.
  • Only the sizes differ per item (w-50 h-24, w-40 h-26, w-24 h-24), and none of those numbers appear in the animation.
  • Neon glows are variables rather than inline filters, so the light scheme sets --svgb-11-glow: none once instead of overriding five utilities.
  • The insert-coin blink is animate-svgb11-blink from a --animate-* token, with steps(1, end) preserved — a hard blink, not a fade.
  • Labels use group-hover:[text-shadow:...] with the same token as the stroke, so a shape and its text always glow the same colour.
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: Tailwind Stroke Draw Button with pathLength Normalization
Source: https://codefronts.com/components/tailwind-svg-stroke-buttons/tailwind-stroke-draw-button-with-pathlength-normalization/

Five silhouettes &mdash; hexagon, diamond, shield, octagon, ring &mdash; animated by one class string, character for character, apart from colour. Because every path carries pathLength="1", [stroke-dasharray:1] [stroke-dashoffset:1] is a complete outline on all of them, which means the string is genuinely reusable: extract it into an @utility and it animates any shape you point it at.
## HTML
```html
<section class="svgb-11 relative block w-full min-h-svh overflow-hidden bg-svgb11-bg text-svgb11-fg font-svgb11-sans">
  <input class="peer absolute opacity-0 pointer-events-none" type="checkbox" id="svgb-11-theme">
  <label class="absolute z-[5] top-4 right-4 grid place-items-center size-11 border border-current/24 bg-current/8 cursor-pointer transition-[background-color,transform] duration-150 hover:bg-current/16 active:scale-95 peer-focus-visible:outline-2 peer-focus-visible:outline-current peer-focus-visible:outline-offset-[3px] peer-checked:[&_svg]:rotate-180" for="svgb-11-theme">
    <svg class="size-[18px] transition-transform duration-[400ms] ease-[cubic-bezier(0.2,0.7,0.3,1)]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><circle cx="12" cy="12" r="8.5"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor" stroke="none"/></svg>
    <span class="absolute size-px overflow-hidden [clip-path:inset(50%)] whitespace-nowrap">Switch colour scheme</span>
  </label>
  <div class="relative min-h-svh flex flex-col items-center justify-center gap-[clamp(14px,3vh,30px)] p-[clamp(22px,5vw,70px)] text-center">
    <p class="m-0 font-svgb11-mono text-[11px] tracking-[0.3em] uppercase text-svgb11-alt animate-svgb11-blink motion-reduce:animate-none">Insert coin &middot; 1 credit</p>
    <h1 class="m-0 text-[clamp(40px,9vw,96px)] font-bold tracking-[-0.02em] uppercase leading-[0.95] [text-shadow:var(--svgb-11-logo-sh)] [&_span]:text-svgb11-stroke">Vector<span>Runner</span></h1>
    <div class="flex flex-wrap justify-center items-center gap-[clamp(12px,2.4vw,26px)]">
      <button class="group relative grid place-items-center border-0 p-0 bg-transparent text-[15px] font-semibold tracking-[0.14em] uppercase cursor-pointer focus-visible:outline-dashed focus-visible:outline-1 focus-visible:outline-svgb11-alt focus-visible:outline-offset-[6px] w-50 h-24" type="button">
        <svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 200 96" aria-hidden="true">
          <path class="fill-none stroke-svgb11-rest [stroke-width:1.6]" d="M40 4H160L196 48L160 92H40L4 48Z" pathLength="1"/>
          <path class="fill-none [stroke-width:2.4] [stroke-linecap:butt] [stroke-linejoin:miter] [stroke-dasharray:1] [stroke-dashoffset:1] transition-[stroke-dashoffset] duration-[620ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] motion-reduce:transition-none motion-reduce:[stroke-dasharray:none] motion-reduce:[stroke-dashoffset:0] motion-reduce:opacity-0 motion-reduce:group-hover:opacity-100 motion-reduce:group-focus-visible:opacity-100 stroke-svgb11-stroke [filter:var(--svgb-11-glow)]" d="M40 4H160L196 48L160 92H40L4 48Z" pathLength="1"/>
        </svg>
        <span class="relative z-[1] transition-[color,text-shadow] duration-[260ms] motion-reduce:transition-none group-hover:text-svgb11-stroke group-hover:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-stroke)_70%,transparent)] group-focus-visible:text-svgb11-stroke group-focus-visible:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-stroke)_70%,transparent)]">Start</span>
      </button>
      <button class="group relative grid place-items-center border-0 p-0 bg-transparent text-[15px] font-semibold tracking-[0.14em] uppercase cursor-pointer focus-visible:outline-dashed focus-visible:outline-1 focus-visible:outline-svgb11-alt focus-visible:outline-offset-[6px] w-40 h-24" type="button">
        <svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 160 96" aria-hidden="true">
          <path class="fill-none stroke-svgb11-rest [stroke-width:1.6]" d="M80 4L156 48L80 92L4 48Z" pathLength="1"/>
          <path class="fill-none [stroke-width:2.4] [stroke-linecap:butt] [stroke-linejoin:miter] [stroke-dasharray:1] [stroke-dashoffset:1] transition-[stroke-dashoffset] duration-[620ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] motion-reduce:transition-none motion-reduce:[stroke-dasharray:none] motion-reduce:[stroke-dashoffset:0] motion-reduce:opacity-0 motion-reduce:group-hover:opacity-100 motion-reduce:group-focus-visible:opacity-100 stroke-svgb11-alt [filter:var(--svgb-11-glow-alt)]" d="M80 4L156 48L80 92L4 48Z" pathLength="1"/>
        </svg>
        <span class="relative z-[1] transition-[color,text-shadow] duration-[260ms] motion-reduce:transition-none group-hover:text-svgb11-alt group-hover:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-alt)_70%,transparent)] group-focus-visible:text-svgb11-alt group-focus-visible:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-alt)_70%,transparent)]">Co-op</span>
      </button>
      <button class="group relative grid place-items-center border-0 p-0 bg-transparent text-[15px] font-semibold tracking-[0.14em] uppercase cursor-pointer focus-visible:outline-dashed focus-visible:outline-1 focus-visible:outline-svgb11-alt focus-visible:outline-offset-[6px] w-40 h-26" type="button">
        <svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 160 104" aria-hidden="true">
          <path class="fill-none stroke-svgb11-rest [stroke-width:1.6]" d="M80 4L152 24V58C152 82 118 96 80 100C42 96 8 82 8 58V24Z" pathLength="1"/>
          <path class="fill-none [stroke-width:2.4] [stroke-linecap:butt] [stroke-linejoin:miter] [stroke-dasharray:1] [stroke-dashoffset:1] transition-[stroke-dashoffset] duration-[620ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] motion-reduce:transition-none motion-reduce:[stroke-dasharray:none] motion-reduce:[stroke-dashoffset:0] motion-reduce:opacity-0 motion-reduce:group-hover:opacity-100 motion-reduce:group-focus-visible:opacity-100 stroke-svgb11-stroke [filter:var(--svgb-11-glow)]" d="M80 4L152 24V58C152 82 118 96 80 100C42 96 8 82 8 58V24Z" pathLength="1"/>
        </svg>
        <span class="relative z-[1] transition-[color,text-shadow] duration-[260ms] motion-reduce:transition-none group-hover:text-svgb11-stroke group-hover:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-stroke)_70%,transparent)] group-focus-visible:text-svgb11-stroke group-focus-visible:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-stroke)_70%,transparent)]">Armory</span>
      </button>
      <button class="group relative grid place-items-center border-0 p-0 bg-transparent text-[15px] font-semibold tracking-[0.14em] uppercase cursor-pointer focus-visible:outline-dashed focus-visible:outline-1 focus-visible:outline-svgb11-alt focus-visible:outline-offset-[6px] w-50 h-24" type="button">
        <svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 200 96" aria-hidden="true">
          <path class="fill-none stroke-svgb11-rest [stroke-width:1.6]" d="M28 4H172L196 28V68L172 92H28L4 68V28Z" pathLength="1"/>
          <path class="fill-none [stroke-width:2.4] [stroke-linecap:butt] [stroke-linejoin:miter] [stroke-dasharray:1] [stroke-dashoffset:1] transition-[stroke-dashoffset] duration-[620ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] motion-reduce:transition-none motion-reduce:[stroke-dasharray:none] motion-reduce:[stroke-dashoffset:0] motion-reduce:opacity-0 motion-reduce:group-hover:opacity-100 motion-reduce:group-focus-visible:opacity-100 stroke-svgb11-stroke [filter:var(--svgb-11-glow)]" d="M28 4H172L196 28V68L172 92H28L4 68V28Z" pathLength="1"/>
        </svg>
        <span class="relative z-[1] transition-[color,text-shadow] duration-[260ms] motion-reduce:transition-none group-hover:text-svgb11-stroke group-hover:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-stroke)_70%,transparent)] group-focus-visible:text-svgb11-stroke group-focus-visible:[text-shadow:0_0_14px_color-mix(in_oklab,var(--color-svgb11-stroke)_70%,transparent)]">Options</span>
      </button>
      <button class="group relative grid place-items-center border-0 p-0 bg-transparent text-[15px] font-semibold tracking-[0.14em] uppercase cursor-pointer focus-visible:outline-dashed focus-visible:outline-1 focus-visible:outline-svgb11-alt focus-visible:outline-offset-[6px] w-24 h-24" type="button" aria-label="Quit to attract mode">
        <svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 96 96" aria-hidden="true">
          <circle class="fill-none stroke-svgb11-rest [stroke-width:1.6]" cx="48" cy="48" r="44" pathLength="1"/>
          <circle class="fill-none [stroke-width:2.4] [stroke-linecap:butt] [stroke-linejoin:miter] [stroke-dasharray:1] [stroke-dashoffset:1] transition-[stroke-dashoffset] duration-[620ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] motion-reduce:transition-none motion-reduce:[stroke-dasharray:none] motion-reduce:[stroke-dashoffset:0] motion-reduce:opacity-0 motion-reduce:group-hover:opacity-100 motion-reduce:group-focus-visible:opacity-100 stroke-svgb11-alt [filter:var(--svgb-11-glow-alt)]" cx="48" cy="48" r="44" pathLength="1"/>
        </svg>
        <span class="relative z-[1] text-[26px] tracking-normal transition-[color,text-shadow] duration-[260ms] motion-reduce:transition-none group-hover:text-svgb11-alt group-focus-visible:text-svgb11-alt">&times;</span>
      </button>
    </div>
    <div class="grid grid-cols-[repeat(4,auto)] gap-x-3.5 gap-y-1 px-[18px] py-3 border border-svgb11-rest font-svgb11-mono text-[10.5px] tracking-[0.1em] uppercase">
      <span class="text-svgb11-muted">dasharray</span><span class="text-svgb11-stroke">1</span>
      <span class="text-svgb11-muted">dashoffset</span><span class="text-svgb11-stroke">1 &rarr; 0</span>
      <span class="text-svgb11-muted">shapes</span><span class="text-svgb11-stroke">5</span>
      <span class="text-svgb11-muted">constants</span><span class="text-svgb11-stroke">0</span>
    </div>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-svgb11-bg: #06070d;
  --color-svgb11-bg: oklch(0.145 0.02 265);
  --color-svgb11-fg: #e8f6ff;
  --color-svgb11-fg: oklch(0.96 0.02 220);
  --color-svgb11-muted: #7d90a8;
  --color-svgb11-muted: oklch(0.645 0.035 250);
  --color-svgb11-rest: rgba(232, 246, 255, 0.16);
  --color-svgb11-rest: oklch(0.96 0.02 220 / 0.16);
  --color-svgb11-stroke: #22f0ff;
  --color-svgb11-stroke: oklch(0.88 0.16 200);
  --color-svgb11-alt: #ff3ecb;
  --color-svgb11-alt: oklch(0.72 0.26 340);
  --font-svgb11-sans: 'Chakra Petch', system-ui, sans-serif;
  --font-svgb11-mono: 'JetBrains Mono', ui-monospace, monospace;
  --animate-svgb11-blink: svgb11-blink 1.8s steps(1, end) infinite;
}

@keyframes svgb11-blink {
  0%,
    60% {
    opacity: 1;
  }

  61%,
    100% {
    opacity: 0.25;
  }
}
/* Glows live in variables so the light scheme can switch them off without
   touching a single utility, and so both tones read the same class shape. */

.svgb-11 {
  --svgb-11-glow: drop-shadow(0 0 7px color-mix(in oklab, var(--color-svgb11-stroke) 60%, transparent));
  --svgb-11-glow-alt: drop-shadow(0 0 7px color-mix(in oklab, var(--color-svgb11-alt) 60%, transparent));
  --svgb-11-logo-sh: 0 0 22px color-mix(in oklab, var(--color-svgb11-stroke) 55%, transparent);
}
/* CRT scanlines and cabinet bloom: two decorative pseudo-element layers */

.svgb-11::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: repeating-linear-gradient(0deg, oklch(0 0 0 / 0.34) 0 1px, transparent 1px 3px);
  opacity: var(--svgb-11-scan, 0.55);
}

.svgb-11::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(60% 50% at 50% 0%, color-mix(in oklab, var(--color-svgb11-stroke) 14%, transparent), transparent 65%), radial-gradient(70% 60% at 50% 110%, color-mix(in oklab, var(--color-svgb11-alt) 14%, transparent), transparent 65%);
}

.svgb-11:has(#svgb-11-theme:checked) {
  --color-svgb11-bg: #eef2f6;
  --color-svgb11-bg: oklch(0.95 0.008 235);
  --color-svgb11-fg: #0b1220;
  --color-svgb11-fg: oklch(0.19 0.025 260);
  --color-svgb11-muted: #4f5f75;
  --color-svgb11-muted: oklch(0.49 0.03 255);
  --color-svgb11-rest: rgba(11, 18, 32, 0.2);
  --color-svgb11-rest: oklch(0.19 0.025 260 / 0.2);
  --color-svgb11-stroke: #006d8f;
  --color-svgb11-stroke: oklch(0.47 0.11 218);
  --color-svgb11-alt: #9c1173;
  --color-svgb11-alt: oklch(0.44 0.20 340);
  --svgb-11-glow: none;
  --svgb-11-glow-alt: none;
  --svgb-11-logo-sh: none;
  --svgb-11-scan: 0.18;
}
```

How this works

Normalise the path and the arithmetic disappears. pathLength tells the renderer to treat a path as being a given length regardless of its real geometry. Set it to 1 and every dash value becomes a fraction:

<path pathLength="1" class="[stroke-dasharray:1] [stroke-dashoffset:1]
      transition-[stroke-dashoffset] duration-[560ms]
      group-hover:[stroke-dashoffset:0]" />

Why this matters more in Tailwind than in CSS. A measured constant makes a class string single-use: [stroke-dasharray:1263] belongs to one path and nothing else. Normalised, the same string is correct for every silhouette in the menu — so it can be lifted into a real utility:

@utility draw-stroke {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 560ms;
}

The sizes are not part of the animation. Each item has its own w-* and h-*, and the draw does not know or care. That is the test of whether a stroke technique is really reusable: change the geometry and nothing in the motion needs editing.

Glows as variables, not five overrides. The arcade look is a drop-shadow per shape. Rather than write five utilities and then five light-mode overrides, the glow is a variable the shapes read — so the light scheme sets it to none once.

A blink that blinks. The insert-coin line uses steps(1, end) inside its --animate-* entry. Preserving the step function is the difference between a CRT blink and a soft pulse; the token keeps that decision in one place.

Make it yours

  • Add a sixth shape. It needs pathLength=&quot;1&quot; and a copy of the same class string — no measurement, no new numbers.
  • Extract the string into @utility draw-stroke once you use it more than twice; this is the one demo in the collection where that is safe.
  • Retime all five from the shared duration-*, or give one shape its own to make it the focal item.
  • Set --svgb-11-glow: none for a flat vector-arcade look, or raise it for more bloom.
  • Use fractions for partial draws: [stroke-dashoffset:0.5] is exactly half an outline on any shape.

Gotchas — read before shipping

  • pathLength must be on the element, not in CSS. It is a presentation attribute with no CSS equivalent, and forgetting it makes [stroke-dasharray:1] render as a 1-unit dash — a nearly invisible dotted line.
  • Normalisation distributes length evenly across the path, so dash rhythm is not uniform on shapes with very unequal segment lengths. Fine for outlines, wrong for tick marks.
  • Fractions need leading zeros: [stroke-dashoffset:0.5], not [stroke-dashoffset:.5].
  • A dash pattern restarts at every M command, so a multi-subpath shape normalises as a whole but draws in segments — see demo 14 for the fix.
  • Extracting a class string into @utility only works while it has no measured values in it. Do not extract the demo 01 string.
  • Glows on five simultaneous shapes are a real paint cost on low-end GPUs; the variable exists partly so you can turn them off.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

pathLength is SVG 1.1 and universally supported — it is the most portable technique in the collection. The floor is Tailwind v4's generated sheet; @utility requires v4 specifically, as v3 had no equivalent.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Plain CSSno Tailwind surface

Search CodeFronts

Loading…