02 / 20
All 20 in 20 Tailwind SVG Stroke Buttons
- 01Tailwind SVG Stroke Draw Button Border on Hover
- 02Tailwind Stroke Dashoffset Button Outline Animation
- 03Tailwind Corner Start Perimeter Draw Button
- 04Tailwind Two Path Split Border Draw Button
- 05Tailwind Rounded Rect Stroke Button with rx Radius
- 06Tailwind Dashed Marching Ants Button Border
- 07Tailwind Gradient Stroke Button with SVG linearGradient
- 08Tailwind Stroke Draw Button That Reverses on Mouse Leave
- 09Tailwind Focus Visible Stroke Outline for Keyboard Users
- 10Tailwind Non Scaling Stroke Button That Resizes with Text
- 11Tailwind Stroke Draw Button with pathLength Normalization
- 12Tailwind Dual Layer Stroke Button with Offset Trailing Path
- 13Tailwind Sequential Stroke and Label Fade Button
- 14Tailwind Icon Plus Border Stroke Draw Together
- 15Tailwind Stroke Draw Submit Button Loading State
- 16Tailwind Stroke Draw Button Disabled and Error States
- 17Tailwind Scroll Triggered Button Stroke with animation-timeline
- 18Tailwind Stroke Draw Button Group with Staggered Delay
- 19Tailwind Reduced Motion Fallback for Stroke Buttons
- 20Tailwind Variable Width Stroke Button with Text Length
Tailwind Stroke Dashoffset Button Outline Animation
Three pads, three jobs for one number — ported so the difference between them is readable in the class lists. Both loops are declared once as --animate-* theme entries, so the markup only says animate-svgb02-orbit; hovering flips [animation-play-state:paused] to running rather than restarting the animation; and the twenty waveform bars share a single class list because h-(--h) reads each bar's own inline variable.
Nucleo: 44,000+ SVG icons in five families · All Icons Bundle $149
Nucleo · Affiliate link
The code
<section class="svgb-02 relative block w-full min-h-svh bg-svgb02-bg text-svgb02-fg font-svgb02-sans">
<input class="peer absolute opacity-0 pointer-events-none" type="checkbox" id="svgb-02-theme">
<label class="absolute z-[5] top-4 right-4 grid place-items-center size-11 rounded-full border border-current/20 bg-current/5 cursor-pointer transition-[background-color,transform] duration-150 hover:bg-current/15 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-02-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="min-h-svh grid place-items-center p-[clamp(20px,5vw,64px)]">
<div class="w-full max-w-160 flex flex-col gap-[clamp(18px,3vw,28px)] p-[clamp(22px,4vw,36px)] rounded-md bg-linear-to-b from-svgb02-rack to-[color-mix(in_oklab,var(--color-svgb02-rack)_80%,#000)] shadow-[inset_0_1px_0_oklch(1_0_0/0.07),0_40px_80px_-50px_#000]">
<div class="flex justify-between items-baseline">
<span class="text-[13px] tracking-[0.14em] uppercase text-svgb02-muted">Take 07</span>
<span class="font-svgb02-mono text-[clamp(20px,3.4vw,30px)] tracking-[-0.01em] tabular-nums text-svgb02-line">00:02:41:18</span>
</div>
<div class="flex items-end gap-1 h-21" aria-hidden="true">
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:22%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:54%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:38%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:76%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:92%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:61%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:44%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:83%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:35%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:67%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:96%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:52%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:29%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:71%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:48%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:88%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:33%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:59%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:41%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:24%"></span>
</div>
<div class="h-1.5">
<svg class="w-full h-1.5 overflow-visible" viewBox="0 0 560 6" preserveAspectRatio="none" aria-hidden="true">
<line class="stroke-svgb02-fg/15 [stroke-width:2]" x1="1" y1="3" x2="559" y2="3" vector-effect="non-scaling-stroke"/>
<line class="stroke-svgb02-line [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:26_534] [stroke-dashoffset:var(--svgb-02-phase)] animate-svgb02-run motion-reduce:animate-none" x1="1" y1="3" x2="559" y2="3" vector-effect="non-scaling-stroke"/>
</svg>
</div>
<div class="flex flex-wrap justify-between gap-[clamp(18px,4vw,40px)] pt-1.5">
<div class="flex flex-col items-center gap-3">
<button class="group relative grid place-items-center size-30 p-0 border-0 rounded-full bg-[radial-gradient(circle_at_50%_30%,color-mix(in_oklab,var(--color-svgb02-fg)_7%,transparent),transparent_70%)] cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb02-line focus-visible:outline-offset-4" type="button" aria-label="Play take">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 120 120" aria-hidden="true">
<circle class="fill-none stroke-svgb02-fg/15 [stroke-width:1.5]" cx="60" cy="60" r="57"/>
<circle class="fill-none stroke-svgb02-line [stroke-width:2.4] [stroke-linecap:round] [stroke-dasharray:360] [stroke-dashoffset:360] transition-[stroke-dashoffset] duration-[760ms] ease-[cubic-bezier(0.16,1,0.3,1)] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] motion-reduce:transition-none motion-reduce:[stroke-dashoffset:0]" cx="60" cy="60" r="57"/>
</svg>
<svg class="relative z-[1] size-[30px] transition-[fill,stroke] duration-300 stroke-none fill-svgb02-fg group-hover:fill-svgb02-line group-focus-visible:fill-svgb02-line" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6.5 18 12l-9 5.5Z"/></svg>
</button>
<span class="font-svgb02-mono text-[10.5px] leading-[1.55] text-center text-svgb02-muted">dasharray 360<br>offset → 0</span>
</div>
<div class="flex flex-col items-center gap-3">
<button class="group relative grid place-items-center size-30 p-0 border-0 rounded-full bg-[radial-gradient(circle_at_50%_30%,color-mix(in_oklab,var(--color-svgb02-fg)_7%,transparent),transparent_70%)] cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb02-line focus-visible:outline-offset-4" type="button" aria-label="Arm recording">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 120 120" aria-hidden="true">
<circle class="fill-none stroke-svgb02-fg/15 [stroke-width:1.5]" cx="60" cy="60" r="57"/>
<circle class="fill-none stroke-svgb02-line [stroke-width:2.4] [stroke-linecap:round] [stroke-dasharray:40_320] [stroke-dashoffset:var(--svgb-02-phase)] animate-svgb02-orbit [animation-play-state:paused] group-hover:[animation-play-state:running] group-focus-visible:[animation-play-state:running] motion-reduce:animate-none" cx="60" cy="60" r="57"/>
</svg>
<svg class="relative z-[1] size-[30px] transition-[fill,stroke] duration-300 fill-none stroke-svgb02-fg [stroke-width:1.6] [stroke-linecap:round] [stroke-linejoin:round] group-hover:stroke-svgb02-line group-focus-visible:stroke-svgb02-line" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="5.5"/></svg>
</button>
<span class="font-svgb02-mono text-[10.5px] leading-[1.55] text-center text-svgb02-muted">dasharray 40 320<br>offset → -360</span>
</div>
<div class="flex flex-col items-center gap-3">
<button class="group relative grid place-items-center size-30 p-0 border-0 rounded-full bg-[radial-gradient(circle_at_50%_30%,color-mix(in_oklab,var(--color-svgb02-fg)_7%,transparent),transparent_70%)] cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb02-line focus-visible:outline-offset-4" type="button" aria-label="Loop region">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 120 120" aria-hidden="true">
<circle class="fill-none stroke-svgb02-fg/15 [stroke-width:1.5]" cx="60" cy="60" r="57"/>
<circle class="fill-none stroke-svgb02-line [stroke-width:2.4] [stroke-linecap:round] [stroke-dasharray:180_180] [stroke-dashoffset:var(--svgb-02-phase)] animate-svgb02-orbit [animation-play-state:paused] group-hover:[animation-play-state:running] group-focus-visible:[animation-play-state:running] motion-reduce:animate-none" cx="60" cy="60" r="57"/>
</svg>
<svg class="relative z-[1] size-[30px] transition-[fill,stroke] duration-300 fill-none stroke-svgb02-fg [stroke-width:1.6] [stroke-linecap:round] [stroke-linejoin:round] group-hover:stroke-svgb02-line group-focus-visible:stroke-svgb02-line" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 9.5A5 5 0 0 1 10 5h8m0 0-3-3m3 3-3 3M19 14.5A5 5 0 0 1 14 19H6m0 0 3 3m-3-3 3-3"/></svg>
</button>
<span class="font-svgb02-mono text-[10.5px] leading-[1.55] text-center text-svgb02-muted">dasharray 180 180<br>offset → -360</span>
</div>
</div>
</div>
</div>
</section><section class="svgb-02 relative block w-full min-h-svh bg-svgb02-bg text-svgb02-fg font-svgb02-sans">
<input class="peer absolute opacity-0 pointer-events-none" type="checkbox" id="svgb-02-theme">
<label class="absolute z-[5] top-4 right-4 grid place-items-center size-11 rounded-full border border-current/20 bg-current/5 cursor-pointer transition-[background-color,transform] duration-150 hover:bg-current/15 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-02-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="min-h-svh grid place-items-center p-[clamp(20px,5vw,64px)]">
<div class="w-full max-w-160 flex flex-col gap-[clamp(18px,3vw,28px)] p-[clamp(22px,4vw,36px)] rounded-md bg-linear-to-b from-svgb02-rack to-[color-mix(in_oklab,var(--color-svgb02-rack)_80%,#000)] shadow-[inset_0_1px_0_oklch(1_0_0/0.07),0_40px_80px_-50px_#000]">
<div class="flex justify-between items-baseline">
<span class="text-[13px] tracking-[0.14em] uppercase text-svgb02-muted">Take 07</span>
<span class="font-svgb02-mono text-[clamp(20px,3.4vw,30px)] tracking-[-0.01em] tabular-nums text-svgb02-line">00:02:41:18</span>
</div>
<div class="flex items-end gap-1 h-21" aria-hidden="true">
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:22%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:54%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:38%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:76%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:92%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:61%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:44%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:83%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:35%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:67%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:96%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:52%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:29%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:71%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:48%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:88%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:33%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:59%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:41%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:24%"></span>
</div>
<div class="h-1.5">
<svg class="w-full h-1.5 overflow-visible" viewBox="0 0 560 6" preserveAspectRatio="none" aria-hidden="true">
<line class="stroke-svgb02-fg/15 [stroke-width:2]" x1="1" y1="3" x2="559" y2="3" vector-effect="non-scaling-stroke"/>
<line class="stroke-svgb02-line [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:26_534] [stroke-dashoffset:var(--svgb-02-phase)] animate-svgb02-run motion-reduce:animate-none" x1="1" y1="3" x2="559" y2="3" vector-effect="non-scaling-stroke"/>
</svg>
</div>
<div class="flex flex-wrap justify-between gap-[clamp(18px,4vw,40px)] pt-1.5">
<div class="flex flex-col items-center gap-3">
<button class="group relative grid place-items-center size-30 p-0 border-0 rounded-full bg-[radial-gradient(circle_at_50%_30%,color-mix(in_oklab,var(--color-svgb02-fg)_7%,transparent),transparent_70%)] cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb02-line focus-visible:outline-offset-4" type="button" aria-label="Play take">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 120 120" aria-hidden="true">
<circle class="fill-none stroke-svgb02-fg/15 [stroke-width:1.5]" cx="60" cy="60" r="57"/>
<circle class="fill-none stroke-svgb02-line [stroke-width:2.4] [stroke-linecap:round] [stroke-dasharray:360] [stroke-dashoffset:360] transition-[stroke-dashoffset] duration-[760ms] ease-[cubic-bezier(0.16,1,0.3,1)] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] motion-reduce:transition-none motion-reduce:[stroke-dashoffset:0]" cx="60" cy="60" r="57"/>
</svg>
<svg class="relative z-[1] size-[30px] transition-[fill,stroke] duration-300 stroke-none fill-svgb02-fg group-hover:fill-svgb02-line group-focus-visible:fill-svgb02-line" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6.5 18 12l-9 5.5Z"/></svg>
</button>
<span class="font-svgb02-mono text-[10.5px] leading-[1.55] text-center text-svgb02-muted">dasharray 360<br>offset → 0</span>
</div>
<div class="flex flex-col items-center gap-3">
<button class="group relative grid place-items-center size-30 p-0 border-0 rounded-full bg-[radial-gradient(circle_at_50%_30%,color-mix(in_oklab,var(--color-svgb02-fg)_7%,transparent),transparent_70%)] cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb02-line focus-visible:outline-offset-4" type="button" aria-label="Arm recording">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 120 120" aria-hidden="true">
<circle class="fill-none stroke-svgb02-fg/15 [stroke-width:1.5]" cx="60" cy="60" r="57"/>
<circle class="fill-none stroke-svgb02-line [stroke-width:2.4] [stroke-linecap:round] [stroke-dasharray:40_320] [stroke-dashoffset:var(--svgb-02-phase)] animate-svgb02-orbit [animation-play-state:paused] group-hover:[animation-play-state:running] group-focus-visible:[animation-play-state:running] motion-reduce:animate-none" cx="60" cy="60" r="57"/>
</svg>
<svg class="relative z-[1] size-[30px] transition-[fill,stroke] duration-300 fill-none stroke-svgb02-fg [stroke-width:1.6] [stroke-linecap:round] [stroke-linejoin:round] group-hover:stroke-svgb02-line group-focus-visible:stroke-svgb02-line" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="5.5"/></svg>
</button>
<span class="font-svgb02-mono text-[10.5px] leading-[1.55] text-center text-svgb02-muted">dasharray 40 320<br>offset → -360</span>
</div>
<div class="flex flex-col items-center gap-3">
<button class="group relative grid place-items-center size-30 p-0 border-0 rounded-full bg-[radial-gradient(circle_at_50%_30%,color-mix(in_oklab,var(--color-svgb02-fg)_7%,transparent),transparent_70%)] cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb02-line focus-visible:outline-offset-4" type="button" aria-label="Loop region">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 120 120" aria-hidden="true">
<circle class="fill-none stroke-svgb02-fg/15 [stroke-width:1.5]" cx="60" cy="60" r="57"/>
<circle class="fill-none stroke-svgb02-line [stroke-width:2.4] [stroke-linecap:round] [stroke-dasharray:180_180] [stroke-dashoffset:var(--svgb-02-phase)] animate-svgb02-orbit [animation-play-state:paused] group-hover:[animation-play-state:running] group-focus-visible:[animation-play-state:running] motion-reduce:animate-none" cx="60" cy="60" r="57"/>
</svg>
<svg class="relative z-[1] size-[30px] transition-[fill,stroke] duration-300 fill-none stroke-svgb02-fg [stroke-width:1.6] [stroke-linecap:round] [stroke-linejoin:round] group-hover:stroke-svgb02-line group-focus-visible:stroke-svgb02-line" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 9.5A5 5 0 0 1 10 5h8m0 0-3-3m3 3-3 3M19 14.5A5 5 0 0 1 14 19H6m0 0 3 3m-3-3 3-3"/></svg>
</button>
<span class="font-svgb02-mono text-[10.5px] leading-[1.55] text-center text-svgb02-muted">dasharray 180 180<br>offset → -360</span>
</div>
</div>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-svgb02-bg: #0a0a0b;
--color-svgb02-bg: oklch(0.15 0.002 60);
--color-svgb02-fg: #f2efe9;
--color-svgb02-fg: oklch(0.955 0.008 80);
--color-svgb02-muted: #8e8981;
--color-svgb02-muted: oklch(0.665 0.012 80);
--color-svgb02-rack: #131211;
--color-svgb02-rack: oklch(0.20 0.004 70);
--color-svgb02-line: #ffb020;
--color-svgb02-line: oklch(0.81 0.16 76);
--font-svgb02-sans: 'Sora', system-ui, sans-serif;
--font-svgb02-mono: 'IBM Plex Mono', ui-monospace, monospace;
/* Named animations become animate-* utilities. The offset moves by exactly
one pattern repeat, so the loop wraps with no visible jump. */
--animate-svgb02-run: svgb02-run 2.6s linear infinite;
--animate-svgb02-orbit: svgb02-orbit 2.4s linear infinite;
}
@property --svgb-02-phase {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
@keyframes svgb02-run {
to {
--svgb-02-phase: -560;
}
}
@keyframes svgb02-orbit {
to {
--svgb-02-phase: -360;
}
}
/* Engines without @property cannot interpolate the registered number, so the
fallback animates the presentation attribute directly. */
@keyframes svgb02-run-fb {
to {
stroke-dashoffset: -560;
}
}
@keyframes svgb02-orbit-fb {
to {
stroke-dashoffset: -360;
}
}
@supports not (background: paint(x)) {
.animate-svgb02-run {
animation-name: svgb02-run-fb;
}
.animate-svgb02-orbit {
animation-name: svgb02-orbit-fb;
}
}
.svgb-02:has(#svgb-02-theme:checked) {
--color-svgb02-bg: #edeae4;
--color-svgb02-bg: oklch(0.94 0.008 85);
--color-svgb02-fg: #14130f;
--color-svgb02-fg: oklch(0.19 0.006 80);
--color-svgb02-muted: #6a655d;
--color-svgb02-muted: oklch(0.535 0.012 80);
--color-svgb02-rack: #fbfaf7;
--color-svgb02-rack: oklch(0.985 0.004 85);
--color-svgb02-line: #8a4b00;
--color-svgb02-line: oklch(0.50 0.13 62);
}@import "tailwindcss";
@theme {
--color-svgb02-bg: #0a0a0b;
--color-svgb02-bg: oklch(0.15 0.002 60);
--color-svgb02-fg: #f2efe9;
--color-svgb02-fg: oklch(0.955 0.008 80);
--color-svgb02-muted: #8e8981;
--color-svgb02-muted: oklch(0.665 0.012 80);
--color-svgb02-rack: #131211;
--color-svgb02-rack: oklch(0.20 0.004 70);
--color-svgb02-line: #ffb020;
--color-svgb02-line: oklch(0.81 0.16 76);
--font-svgb02-sans: 'Sora', system-ui, sans-serif;
--font-svgb02-mono: 'IBM Plex Mono', ui-monospace, monospace;
/* Named animations become animate-* utilities. The offset moves by exactly
one pattern repeat, so the loop wraps with no visible jump. */
--animate-svgb02-run: svgb02-run 2.6s linear infinite;
--animate-svgb02-orbit: svgb02-orbit 2.4s linear infinite;
}
@property --svgb-02-phase {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
@keyframes svgb02-run {
to {
--svgb-02-phase: -560;
}
}
@keyframes svgb02-orbit {
to {
--svgb-02-phase: -360;
}
}
/* Engines without @property cannot interpolate the registered number, so the
fallback animates the presentation attribute directly. */
@keyframes svgb02-run-fb {
to {
stroke-dashoffset: -560;
}
}
@keyframes svgb02-orbit-fb {
to {
stroke-dashoffset: -360;
}
}
@supports not (background: paint(x)) {
.animate-svgb02-run {
animation-name: svgb02-run-fb;
}
.animate-svgb02-orbit {
animation-name: svgb02-orbit-fb;
}
}
.svgb-02:has(#svgb-02-theme:checked) {
--color-svgb02-bg: #edeae4;
--color-svgb02-bg: oklch(0.94 0.008 85);
--color-svgb02-fg: #14130f;
--color-svgb02-fg: oklch(0.19 0.006 80);
--color-svgb02-muted: #6a655d;
--color-svgb02-muted: oklch(0.535 0.012 80);
--color-svgb02-rack: #fbfaf7;
--color-svgb02-rack: oklch(0.985 0.004 85);
--color-svgb02-line: #8a4b00;
--color-svgb02-line: oklch(0.50 0.13 62);
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<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.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-svgb02-bg: #0a0a0b;
--color-svgb02-bg: oklch(0.15 0.002 60);
--color-svgb02-fg: #f2efe9;
--color-svgb02-fg: oklch(0.955 0.008 80);
--color-svgb02-muted: #8e8981;
--color-svgb02-muted: oklch(0.665 0.012 80);
--color-svgb02-rack: #131211;
--color-svgb02-rack: oklch(0.20 0.004 70);
--color-svgb02-line: #ffb020;
--color-svgb02-line: oklch(0.81 0.16 76);
--font-svgb02-sans: 'Sora', system-ui, sans-serif;
--font-svgb02-mono: 'IBM Plex Mono', ui-monospace, monospace;
/* Named animations become animate-* utilities. The offset moves by exactly
one pattern repeat, so the loop wraps with no visible jump. */
--animate-svgb02-run: svgb02-run 2.6s linear infinite;
--animate-svgb02-orbit: svgb02-orbit 2.4s linear infinite;
}
@property --svgb-02-phase {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
@keyframes svgb02-run {
to {
--svgb-02-phase: -560;
}
}
@keyframes svgb02-orbit {
to {
--svgb-02-phase: -360;
}
}
/* Engines without @property cannot interpolate the registered number, so the
fallback animates the presentation attribute directly. */
@keyframes svgb02-run-fb {
to {
stroke-dashoffset: -560;
}
}
@keyframes svgb02-orbit-fb {
to {
stroke-dashoffset: -360;
}
}
@supports not (background: paint(x)) {
.animate-svgb02-run {
animation-name: svgb02-run-fb;
}
.animate-svgb02-orbit {
animation-name: svgb02-orbit-fb;
}
}
.svgb-02:has(#svgb-02-theme:checked) {
--color-svgb02-bg: #edeae4;
--color-svgb02-bg: oklch(0.94 0.008 85);
--color-svgb02-fg: #14130f;
--color-svgb02-fg: oklch(0.19 0.006 80);
--color-svgb02-muted: #6a655d;
--color-svgb02-muted: oklch(0.535 0.012 80);
--color-svgb02-rack: #fbfaf7;
--color-svgb02-rack: oklch(0.985 0.004 85);
--color-svgb02-line: #8a4b00;
--color-svgb02-line: oklch(0.50 0.13 62);
}@import "tailwindcss";
@theme {
--color-svgb02-bg: #0a0a0b;
--color-svgb02-bg: oklch(0.15 0.002 60);
--color-svgb02-fg: #f2efe9;
--color-svgb02-fg: oklch(0.955 0.008 80);
--color-svgb02-muted: #8e8981;
--color-svgb02-muted: oklch(0.665 0.012 80);
--color-svgb02-rack: #131211;
--color-svgb02-rack: oklch(0.20 0.004 70);
--color-svgb02-line: #ffb020;
--color-svgb02-line: oklch(0.81 0.16 76);
--font-svgb02-sans: 'Sora', system-ui, sans-serif;
--font-svgb02-mono: 'IBM Plex Mono', ui-monospace, monospace;
/* Named animations become animate-* utilities. The offset moves by exactly
one pattern repeat, so the loop wraps with no visible jump. */
--animate-svgb02-run: svgb02-run 2.6s linear infinite;
--animate-svgb02-orbit: svgb02-orbit 2.4s linear infinite;
}
@property --svgb-02-phase {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
@keyframes svgb02-run {
to {
--svgb-02-phase: -560;
}
}
@keyframes svgb02-orbit {
to {
--svgb-02-phase: -360;
}
}
/* Engines without @property cannot interpolate the registered number, so the
fallback animates the presentation attribute directly. */
@keyframes svgb02-run-fb {
to {
stroke-dashoffset: -560;
}
}
@keyframes svgb02-orbit-fb {
to {
stroke-dashoffset: -360;
}
}
@supports not (background: paint(x)) {
.animate-svgb02-run {
animation-name: svgb02-run-fb;
}
.animate-svgb02-orbit {
animation-name: svgb02-orbit-fb;
}
}
.svgb-02:has(#svgb-02-theme:checked) {
--color-svgb02-bg: #edeae4;
--color-svgb02-bg: oklch(0.94 0.008 85);
--color-svgb02-fg: #14130f;
--color-svgb02-fg: oklch(0.19 0.006 80);
--color-svgb02-muted: #6a655d;
--color-svgb02-muted: oklch(0.535 0.012 80);
--color-svgb02-rack: #fbfaf7;
--color-svgb02-rack: oklch(0.985 0.004 85);
--color-svgb02-line: #8a4b00;
--color-svgb02-line: oklch(0.50 0.13 62);
}<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=Sora:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap">
<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=Sora:wght@400;500;600&family=IBM+Plex+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 insetup, not as an@importinside the stylesheet. Tailwind emits user@importrules after its own@layerblocks, and a late@importis invalid CSS — the browser drops it and the demo silently falls back to system fonts. @property --svgb-02-phasestays in the stylesheet; registered properties are a CSS feature with no utility equivalent, and the whole demo depends on that number being interpolable.- Both loops are declared once in
@themeas--animate-svgb02-orbitand--animate-svgb02-run, so the markup just saysanimate-svgb02-orbit. - Pause and resume are arbitrary properties on the path —
[animation-play-state:paused] group-hover:[animation-play-state:running]— which keeps the animation's own timeline instead of restarting it. - Dash patterns use the underscore escape for the space:
[stroke-dasharray:40_320]compiles to40 320. - The waveform bars read their own inline height with
h-(--h), Tailwind v4's shorthand forh-[var(--h)], so twenty bars need one class list. motion-reduce:animate-nonestops every travelling segment; the resting hairline underneath is a separate path and stays visible.
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 Dashoffset Button Outline Animation
Source: https://codefronts.com/components/tailwind-svg-stroke-buttons/tailwind-stroke-dashoffset-button-outline-animation/
Three pads, three jobs for one number — ported so the difference between them is readable in the class lists. Both loops are declared once as --animate-* theme entries, so the markup only says animate-svgb02-orbit; hovering flips [animation-play-state:paused] to running rather than restarting the animation; and the twenty waveform bars share a single class list because h-(--h) reads each bar's own inline variable.
## HTML
```html
<section class="svgb-02 relative block w-full min-h-svh bg-svgb02-bg text-svgb02-fg font-svgb02-sans">
<input class="peer absolute opacity-0 pointer-events-none" type="checkbox" id="svgb-02-theme">
<label class="absolute z-[5] top-4 right-4 grid place-items-center size-11 rounded-full border border-current/20 bg-current/5 cursor-pointer transition-[background-color,transform] duration-150 hover:bg-current/15 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-02-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="min-h-svh grid place-items-center p-[clamp(20px,5vw,64px)]">
<div class="w-full max-w-160 flex flex-col gap-[clamp(18px,3vw,28px)] p-[clamp(22px,4vw,36px)] rounded-md bg-linear-to-b from-svgb02-rack to-[color-mix(in_oklab,var(--color-svgb02-rack)_80%,#000)] shadow-[inset_0_1px_0_oklch(1_0_0/0.07),0_40px_80px_-50px_#000]">
<div class="flex justify-between items-baseline">
<span class="text-[13px] tracking-[0.14em] uppercase text-svgb02-muted">Take 07</span>
<span class="font-svgb02-mono text-[clamp(20px,3.4vw,30px)] tracking-[-0.01em] tabular-nums text-svgb02-line">00:02:41:18</span>
</div>
<div class="flex items-end gap-1 h-21" aria-hidden="true">
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:22%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:54%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:38%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:76%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:92%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:61%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:44%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:83%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:35%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:67%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:96%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:52%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:29%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:71%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:48%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:88%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:33%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:59%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:41%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:24%"></span>
</div>
<div class="h-1.5">
<svg class="w-full h-1.5 overflow-visible" viewBox="0 0 560 6" preserveAspectRatio="none" aria-hidden="true">
<line class="stroke-svgb02-fg/15 [stroke-width:2]" x1="1" y1="3" x2="559" y2="3" vector-effect="non-scaling-stroke"/>
<line class="stroke-svgb02-line [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:26_534] [stroke-dashoffset:var(--svgb-02-phase)] animate-svgb02-run motion-reduce:animate-none" x1="1" y1="3" x2="559" y2="3" vector-effect="non-scaling-stroke"/>
</svg>
</div>
<div class="flex flex-wrap justify-between gap-[clamp(18px,4vw,40px)] pt-1.5">
<div class="flex flex-col items-center gap-3">
<button class="group relative grid place-items-center size-30 p-0 border-0 rounded-full bg-[radial-gradient(circle_at_50%_30%,color-mix(in_oklab,var(--color-svgb02-fg)_7%,transparent),transparent_70%)] cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb02-line focus-visible:outline-offset-4" type="button" aria-label="Play take">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 120 120" aria-hidden="true">
<circle class="fill-none stroke-svgb02-fg/15 [stroke-width:1.5]" cx="60" cy="60" r="57"/>
<circle class="fill-none stroke-svgb02-line [stroke-width:2.4] [stroke-linecap:round] [stroke-dasharray:360] [stroke-dashoffset:360] transition-[stroke-dashoffset] duration-[760ms] ease-[cubic-bezier(0.16,1,0.3,1)] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] motion-reduce:transition-none motion-reduce:[stroke-dashoffset:0]" cx="60" cy="60" r="57"/>
</svg>
<svg class="relative z-[1] size-[30px] transition-[fill,stroke] duration-300 stroke-none fill-svgb02-fg group-hover:fill-svgb02-line group-focus-visible:fill-svgb02-line" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6.5 18 12l-9 5.5Z"/></svg>
</button>
<span class="font-svgb02-mono text-[10.5px] leading-[1.55] text-center text-svgb02-muted">dasharray 360<br>offset → 0</span>
</div>
<div class="flex flex-col items-center gap-3">
<button class="group relative grid place-items-center size-30 p-0 border-0 rounded-full bg-[radial-gradient(circle_at_50%_30%,color-mix(in_oklab,var(--color-svgb02-fg)_7%,transparent),transparent_70%)] cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb02-line focus-visible:outline-offset-4" type="button" aria-label="Arm recording">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 120 120" aria-hidden="true">
<circle class="fill-none stroke-svgb02-fg/15 [stroke-width:1.5]" cx="60" cy="60" r="57"/>
<circle class="fill-none stroke-svgb02-line [stroke-width:2.4] [stroke-linecap:round] [stroke-dasharray:40_320] [stroke-dashoffset:var(--svgb-02-phase)] animate-svgb02-orbit [animation-play-state:paused] group-hover:[animation-play-state:running] group-focus-visible:[animation-play-state:running] motion-reduce:animate-none" cx="60" cy="60" r="57"/>
</svg>
<svg class="relative z-[1] size-[30px] transition-[fill,stroke] duration-300 fill-none stroke-svgb02-fg [stroke-width:1.6] [stroke-linecap:round] [stroke-linejoin:round] group-hover:stroke-svgb02-line group-focus-visible:stroke-svgb02-line" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="5.5"/></svg>
</button>
<span class="font-svgb02-mono text-[10.5px] leading-[1.55] text-center text-svgb02-muted">dasharray 40 320<br>offset → -360</span>
</div>
<div class="flex flex-col items-center gap-3">
<button class="group relative grid place-items-center size-30 p-0 border-0 rounded-full bg-[radial-gradient(circle_at_50%_30%,color-mix(in_oklab,var(--color-svgb02-fg)_7%,transparent),transparent_70%)] cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb02-line focus-visible:outline-offset-4" type="button" aria-label="Loop region">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 120 120" aria-hidden="true">
<circle class="fill-none stroke-svgb02-fg/15 [stroke-width:1.5]" cx="60" cy="60" r="57"/>
<circle class="fill-none stroke-svgb02-line [stroke-width:2.4] [stroke-linecap:round] [stroke-dasharray:180_180] [stroke-dashoffset:var(--svgb-02-phase)] animate-svgb02-orbit [animation-play-state:paused] group-hover:[animation-play-state:running] group-focus-visible:[animation-play-state:running] motion-reduce:animate-none" cx="60" cy="60" r="57"/>
</svg>
<svg class="relative z-[1] size-[30px] transition-[fill,stroke] duration-300 fill-none stroke-svgb02-fg [stroke-width:1.6] [stroke-linecap:round] [stroke-linejoin:round] group-hover:stroke-svgb02-line group-focus-visible:stroke-svgb02-line" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 9.5A5 5 0 0 1 10 5h8m0 0-3-3m3 3-3 3M19 14.5A5 5 0 0 1 14 19H6m0 0 3 3m-3-3 3-3"/></svg>
</button>
<span class="font-svgb02-mono text-[10.5px] leading-[1.55] text-center text-svgb02-muted">dasharray 180 180<br>offset → -360</span>
</div>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-svgb02-bg: #0a0a0b;
--color-svgb02-bg: oklch(0.15 0.002 60);
--color-svgb02-fg: #f2efe9;
--color-svgb02-fg: oklch(0.955 0.008 80);
--color-svgb02-muted: #8e8981;
--color-svgb02-muted: oklch(0.665 0.012 80);
--color-svgb02-rack: #131211;
--color-svgb02-rack: oklch(0.20 0.004 70);
--color-svgb02-line: #ffb020;
--color-svgb02-line: oklch(0.81 0.16 76);
--font-svgb02-sans: 'Sora', system-ui, sans-serif;
--font-svgb02-mono: 'IBM Plex Mono', ui-monospace, monospace;
/* Named animations become animate-* utilities. The offset moves by exactly
one pattern repeat, so the loop wraps with no visible jump. */
--animate-svgb02-run: svgb02-run 2.6s linear infinite;
--animate-svgb02-orbit: svgb02-orbit 2.4s linear infinite;
}
@property --svgb-02-phase {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
@keyframes svgb02-run {
to {
--svgb-02-phase: -560;
}
}
@keyframes svgb02-orbit {
to {
--svgb-02-phase: -360;
}
}
/* Engines without @property cannot interpolate the registered number, so the
fallback animates the presentation attribute directly. */
@keyframes svgb02-run-fb {
to {
stroke-dashoffset: -560;
}
}
@keyframes svgb02-orbit-fb {
to {
stroke-dashoffset: -360;
}
}
@supports not (background: paint(x)) {
.animate-svgb02-run {
animation-name: svgb02-run-fb;
}
.animate-svgb02-orbit {
animation-name: svgb02-orbit-fb;
}
}
.svgb-02:has(#svgb-02-theme:checked) {
--color-svgb02-bg: #edeae4;
--color-svgb02-bg: oklch(0.94 0.008 85);
--color-svgb02-fg: #14130f;
--color-svgb02-fg: oklch(0.19 0.006 80);
--color-svgb02-muted: #6a655d;
--color-svgb02-muted: oklch(0.535 0.012 80);
--color-svgb02-rack: #fbfaf7;
--color-svgb02-rack: oklch(0.985 0.004 85);
--color-svgb02-line: #8a4b00;
--color-svgb02-line: oklch(0.50 0.13 62);
}
```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 Dashoffset Button Outline Animation
Source: https://codefronts.com/components/tailwind-svg-stroke-buttons/tailwind-stroke-dashoffset-button-outline-animation/
Three pads, three jobs for one number — ported so the difference between them is readable in the class lists. Both loops are declared once as --animate-* theme entries, so the markup only says animate-svgb02-orbit; hovering flips [animation-play-state:paused] to running rather than restarting the animation; and the twenty waveform bars share a single class list because h-(--h) reads each bar's own inline variable.
## HTML
```html
<section class="svgb-02 relative block w-full min-h-svh bg-svgb02-bg text-svgb02-fg font-svgb02-sans">
<input class="peer absolute opacity-0 pointer-events-none" type="checkbox" id="svgb-02-theme">
<label class="absolute z-[5] top-4 right-4 grid place-items-center size-11 rounded-full border border-current/20 bg-current/5 cursor-pointer transition-[background-color,transform] duration-150 hover:bg-current/15 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-02-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="min-h-svh grid place-items-center p-[clamp(20px,5vw,64px)]">
<div class="w-full max-w-160 flex flex-col gap-[clamp(18px,3vw,28px)] p-[clamp(22px,4vw,36px)] rounded-md bg-linear-to-b from-svgb02-rack to-[color-mix(in_oklab,var(--color-svgb02-rack)_80%,#000)] shadow-[inset_0_1px_0_oklch(1_0_0/0.07),0_40px_80px_-50px_#000]">
<div class="flex justify-between items-baseline">
<span class="text-[13px] tracking-[0.14em] uppercase text-svgb02-muted">Take 07</span>
<span class="font-svgb02-mono text-[clamp(20px,3.4vw,30px)] tracking-[-0.01em] tabular-nums text-svgb02-line">00:02:41:18</span>
</div>
<div class="flex items-end gap-1 h-21" aria-hidden="true">
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:22%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:54%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:38%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:76%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:92%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:61%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:44%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:83%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:35%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:67%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:96%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:52%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:29%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:71%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:48%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:88%"></span>
<span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:33%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:59%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:41%"></span><span class="flex-1 h-(--h) rounded-[1px] bg-svgb02-fg/20" style="--h:24%"></span>
</div>
<div class="h-1.5">
<svg class="w-full h-1.5 overflow-visible" viewBox="0 0 560 6" preserveAspectRatio="none" aria-hidden="true">
<line class="stroke-svgb02-fg/15 [stroke-width:2]" x1="1" y1="3" x2="559" y2="3" vector-effect="non-scaling-stroke"/>
<line class="stroke-svgb02-line [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:26_534] [stroke-dashoffset:var(--svgb-02-phase)] animate-svgb02-run motion-reduce:animate-none" x1="1" y1="3" x2="559" y2="3" vector-effect="non-scaling-stroke"/>
</svg>
</div>
<div class="flex flex-wrap justify-between gap-[clamp(18px,4vw,40px)] pt-1.5">
<div class="flex flex-col items-center gap-3">
<button class="group relative grid place-items-center size-30 p-0 border-0 rounded-full bg-[radial-gradient(circle_at_50%_30%,color-mix(in_oklab,var(--color-svgb02-fg)_7%,transparent),transparent_70%)] cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb02-line focus-visible:outline-offset-4" type="button" aria-label="Play take">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 120 120" aria-hidden="true">
<circle class="fill-none stroke-svgb02-fg/15 [stroke-width:1.5]" cx="60" cy="60" r="57"/>
<circle class="fill-none stroke-svgb02-line [stroke-width:2.4] [stroke-linecap:round] [stroke-dasharray:360] [stroke-dashoffset:360] transition-[stroke-dashoffset] duration-[760ms] ease-[cubic-bezier(0.16,1,0.3,1)] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] motion-reduce:transition-none motion-reduce:[stroke-dashoffset:0]" cx="60" cy="60" r="57"/>
</svg>
<svg class="relative z-[1] size-[30px] transition-[fill,stroke] duration-300 stroke-none fill-svgb02-fg group-hover:fill-svgb02-line group-focus-visible:fill-svgb02-line" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 6.5 18 12l-9 5.5Z"/></svg>
</button>
<span class="font-svgb02-mono text-[10.5px] leading-[1.55] text-center text-svgb02-muted">dasharray 360<br>offset → 0</span>
</div>
<div class="flex flex-col items-center gap-3">
<button class="group relative grid place-items-center size-30 p-0 border-0 rounded-full bg-[radial-gradient(circle_at_50%_30%,color-mix(in_oklab,var(--color-svgb02-fg)_7%,transparent),transparent_70%)] cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb02-line focus-visible:outline-offset-4" type="button" aria-label="Arm recording">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 120 120" aria-hidden="true">
<circle class="fill-none stroke-svgb02-fg/15 [stroke-width:1.5]" cx="60" cy="60" r="57"/>
<circle class="fill-none stroke-svgb02-line [stroke-width:2.4] [stroke-linecap:round] [stroke-dasharray:40_320] [stroke-dashoffset:var(--svgb-02-phase)] animate-svgb02-orbit [animation-play-state:paused] group-hover:[animation-play-state:running] group-focus-visible:[animation-play-state:running] motion-reduce:animate-none" cx="60" cy="60" r="57"/>
</svg>
<svg class="relative z-[1] size-[30px] transition-[fill,stroke] duration-300 fill-none stroke-svgb02-fg [stroke-width:1.6] [stroke-linecap:round] [stroke-linejoin:round] group-hover:stroke-svgb02-line group-focus-visible:stroke-svgb02-line" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="5.5"/></svg>
</button>
<span class="font-svgb02-mono text-[10.5px] leading-[1.55] text-center text-svgb02-muted">dasharray 40 320<br>offset → -360</span>
</div>
<div class="flex flex-col items-center gap-3">
<button class="group relative grid place-items-center size-30 p-0 border-0 rounded-full bg-[radial-gradient(circle_at_50%_30%,color-mix(in_oklab,var(--color-svgb02-fg)_7%,transparent),transparent_70%)] cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb02-line focus-visible:outline-offset-4" type="button" aria-label="Loop region">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 120 120" aria-hidden="true">
<circle class="fill-none stroke-svgb02-fg/15 [stroke-width:1.5]" cx="60" cy="60" r="57"/>
<circle class="fill-none stroke-svgb02-line [stroke-width:2.4] [stroke-linecap:round] [stroke-dasharray:180_180] [stroke-dashoffset:var(--svgb-02-phase)] animate-svgb02-orbit [animation-play-state:paused] group-hover:[animation-play-state:running] group-focus-visible:[animation-play-state:running] motion-reduce:animate-none" cx="60" cy="60" r="57"/>
</svg>
<svg class="relative z-[1] size-[30px] transition-[fill,stroke] duration-300 fill-none stroke-svgb02-fg [stroke-width:1.6] [stroke-linecap:round] [stroke-linejoin:round] group-hover:stroke-svgb02-line group-focus-visible:stroke-svgb02-line" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 9.5A5 5 0 0 1 10 5h8m0 0-3-3m3 3-3 3M19 14.5A5 5 0 0 1 14 19H6m0 0 3 3m-3-3 3-3"/></svg>
</button>
<span class="font-svgb02-mono text-[10.5px] leading-[1.55] text-center text-svgb02-muted">dasharray 180 180<br>offset → -360</span>
</div>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-svgb02-bg: #0a0a0b;
--color-svgb02-bg: oklch(0.15 0.002 60);
--color-svgb02-fg: #f2efe9;
--color-svgb02-fg: oklch(0.955 0.008 80);
--color-svgb02-muted: #8e8981;
--color-svgb02-muted: oklch(0.665 0.012 80);
--color-svgb02-rack: #131211;
--color-svgb02-rack: oklch(0.20 0.004 70);
--color-svgb02-line: #ffb020;
--color-svgb02-line: oklch(0.81 0.16 76);
--font-svgb02-sans: 'Sora', system-ui, sans-serif;
--font-svgb02-mono: 'IBM Plex Mono', ui-monospace, monospace;
/* Named animations become animate-* utilities. The offset moves by exactly
one pattern repeat, so the loop wraps with no visible jump. */
--animate-svgb02-run: svgb02-run 2.6s linear infinite;
--animate-svgb02-orbit: svgb02-orbit 2.4s linear infinite;
}
@property --svgb-02-phase {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
@keyframes svgb02-run {
to {
--svgb-02-phase: -560;
}
}
@keyframes svgb02-orbit {
to {
--svgb-02-phase: -360;
}
}
/* Engines without @property cannot interpolate the registered number, so the
fallback animates the presentation attribute directly. */
@keyframes svgb02-run-fb {
to {
stroke-dashoffset: -560;
}
}
@keyframes svgb02-orbit-fb {
to {
stroke-dashoffset: -360;
}
}
@supports not (background: paint(x)) {
.animate-svgb02-run {
animation-name: svgb02-run-fb;
}
.animate-svgb02-orbit {
animation-name: svgb02-orbit-fb;
}
}
.svgb-02:has(#svgb-02-theme:checked) {
--color-svgb02-bg: #edeae4;
--color-svgb02-bg: oklch(0.94 0.008 85);
--color-svgb02-fg: #14130f;
--color-svgb02-fg: oklch(0.19 0.006 80);
--color-svgb02-muted: #6a655d;
--color-svgb02-muted: oklch(0.535 0.012 80);
--color-svgb02-rack: #fbfaf7;
--color-svgb02-rack: oklch(0.985 0.004 85);
--color-svgb02-line: #8a4b00;
--color-svgb02-line: oklch(0.50 0.13 62);
}
```How this works
Animations are theme entries in v4. An --animate-* token holds the whole shorthand, and the matching @keyframes sits in the same stylesheet. Declare it once, use it as a utility everywhere:
@theme {
--animate-svgb02-orbit: svgb02-orbit 2.4s linear infinite;
--animate-svgb02-run: svgb02-run 3.2s linear infinite;
}
@keyframes svgb02-orbit { to { --svgb-02-phase: -360; } }The registered property is not portable to a utility, and should not be. @property --svgb-02-phase { syntax: '<number>'; inherits: false; initial-value: 0; } is what lets the browser tween the offset instead of snapping it. There is no utility for a property registration, so it stays as CSS — and the paths read it with [stroke-dashoffset:var(--svgb-02-phase)].
Pause, do not restart. The two looping pads run their animation from load and hold it paused, then hover sets it running. Toggling animate-* on and off instead would restart the loop from frame zero and lose the phase:
class="animate-svgb02-orbit [animation-play-state:paused]
group-hover:[animation-play-state:running]
group-focus-visible:[animation-play-state:running]"Three dash patterns, one mechanism. Play is [stroke-dasharray:360] [stroke-dashoffset:360] with a transition — a line that draws once and can reverse. Record is [stroke-dasharray:40_320], a single runner orbiting forever. Loop is [stroke-dasharray:180_180], two marks chasing each other. Same 360-unit circumference, same keyframe; only the pattern changed.
Repetition without repetition. The waveform is twenty <span>s with identical classes and an inline --h. h-(--h) is the v4 shorthand for reading it, which keeps the bars as data rather than twenty hand-written heights.
Make it yours
- Change loop speed in the
@themeentry, not the markup —--animate-svgb02-orbit: svgb02-orbit 1.6s linear infiniteretimes both looping pads at once. - Lengthen the runner by editing
[stroke-dasharray:40_320]; keep the two numbers summing to 360 or the loop stutters at the wrap. - Reverse the orbit with
[animation-direction:reverse]on the path instead of rewriting keyframes. - Give the scrub line its own theme entry if you want it independently retimed; sharing
--animate-svgb02-runis what keeps it locked to the transport now. - Swap the pads' radial-gradient background for a flat
bg-svgb02-fg/5if the demo sits on a busy surface.
Gotchas — read before shipping
- Toggling the
animate-*utility on hover restarts the animation; only[animation-play-state:*]resumes it in place. This is the difference between a runner that keeps orbiting and one that jumps back to the top every time. - The keyframe end value must equal one whole pattern repeat — the sum of the dasharray.
40 320repeats every 360, so the keyframe goes to-360; get this wrong and the loop visibly jumps once per cycle. @propertycannot be expressed as a utility and must not be dropped in the port. Without the registration the offset animates in discrete jumps, or not at all.- Underscores are the space escape, so a genuine underscore in an arbitrary value needs escaping as
\_. h-(--h)is v4-only syntax; on v3 writeh-[var(--h)].- An infinite loop on a resting control is a real attention cost. These two only run on hover and focus for that reason, and
motion-reduce:animate-nonestops them outright.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
The floor is set twice over here: @property (Chrome 85, Safari 16.4, Firefox 128) is what makes the phase interpolable, and Tailwind v4's own generated sheet needs roughly the same engines. Without @property the looping pads fall back to a static dashed ring, which still reads as a control.