04 / 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 Two Path Split Border Draw Button
Two halves, one constant — and in Tailwind the constant is literally shared, because both paths carry the same class list character for character. The seam flash is built entirely from pseudo-element variants, from after:content-[''] through hover:after:delay-[620ms], where the delay equals the draw duration so the tear lands exactly as the two heads meet.
Nucleo: 44,000+ SVG icons in five families · All Icons Bundle $149
Nucleo · Affiliate link
The code
<section class="svgb-04 relative block w-full min-h-svh overflow-hidden bg-svgb04-bg text-svgb04-fg font-svgb04-sans">
<input class="peer absolute opacity-0 pointer-events-none" type="checkbox" id="svgb-04-theme">
<label class="absolute z-[5] top-4 right-4 grid place-items-center size-11 rounded-full border border-current/20 bg-current/8 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-04-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 grid place-items-center p-[clamp(24px,6vw,72px)]">
<div class="flex flex-col items-center gap-[clamp(12px,2vw,18px)] text-center">
<p class="m-0 font-svgb04-mono text-[11px] tracking-[0.34em] uppercase text-svgb04-stroke">Now showing</p>
<h1 class="m-0 font-svgb04-display text-[clamp(46px,11vw,116px)] leading-[0.88] tracking-[-0.02em] uppercase">The Long<br>Exposure</h1>
<p class="m-0 mb-[clamp(10px,2vw,18px)] text-[13px] tracking-[0.12em] uppercase text-svgb04-muted">70mm · 142 min · Screen 4 · 20:45</p>
<button class="group relative w-[min(300px,92vw)] h-23 flex items-stretch border-0 p-0 bg-svgb04-fg/5 cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb04-stroke focus-visible:outline-offset-[5px] after:content-[''] after:absolute after:inset-x-[42%] after:-bottom-[7px] after:h-0.5 after:bg-svgb04-stroke after:opacity-0 after:scale-x-[0.2] after:[transition:opacity_200ms_ease,transform_320ms_var(--ease-svgb04)] hover:after:opacity-90 hover:after:scale-x-100 hover:after:delay-[620ms] focus-visible:after:opacity-90 focus-visible:after:scale-x-100 focus-visible:after:delay-[620ms] motion-reduce:after:transition-none" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 300 92" aria-hidden="true">
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M150 2H290A8 8 0 0 1 298 10V37A9 9 0 0 0 298 55V82A8 8 0 0 1 290 90H150" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M150 2H10A8 8 0 0 0 2 10V37A9 9 0 0 1 2 55V82A8 8 0 0 0 10 90H150" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4] [stroke-dasharray:3_5] [stroke-linecap:round]" d="M150 12V80" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-stroke [stroke-width:2] [stroke-linecap:butt] [stroke-dasharray:392] [stroke-dashoffset:392] transition-[stroke-dashoffset] duration-[620ms] ease-svgb04 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" d="M150 2H290A8 8 0 0 1 298 10V37A9 9 0 0 0 298 55V82A8 8 0 0 1 290 90H150" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-stroke [stroke-width:2] [stroke-linecap:butt] [stroke-dasharray:392] [stroke-dashoffset:392] transition-[stroke-dashoffset] duration-[620ms] ease-svgb04 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" d="M150 2H10A8 8 0 0 0 2 10V37A9 9 0 0 1 2 55V82A8 8 0 0 0 10 90H150" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1] flex-1 flex flex-col justify-center gap-1">
<span class="font-svgb04-mono text-[9.5px] tracking-[0.2em] uppercase text-svgb04-muted">Seat</span>
<span class="text-[19px] font-bold tracking-[-0.01em] transition-colors duration-300 motion-reduce:transition-none group-hover:text-svgb04-stroke group-focus-visible:text-svgb04-stroke">J-14</span>
</span>
<span class="relative z-[1] flex-[1.4] flex flex-col justify-center gap-1">
<span class="font-svgb04-mono text-[9.5px] tracking-[0.2em] uppercase text-svgb04-muted">Admit one</span>
<span class="text-[19px] font-bold tracking-[-0.01em] transition-colors duration-300 motion-reduce:transition-none group-hover:text-svgb04-stroke group-focus-visible:text-svgb04-stroke">Reserve</span>
</span>
</button>
<div class="flex flex-col items-center gap-2.5 mt-[clamp(14px,3vw,24px)]">
<button class="group relative w-44 h-13 grid place-items-center border-0 p-0 bg-transparent text-[13.5px] font-medium cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb04-stroke focus-visible:outline-offset-4" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 176 52" aria-hidden="true">
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M2 26V10A8 8 0 0 1 10 2H166A8 8 0 0 1 174 10V26" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M2 26V42A8 8 0 0 0 10 50H166A8 8 0 0 0 174 42V26" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-fg [stroke-width:1.8] [stroke-linecap:butt] [stroke-dasharray:218] [stroke-dashoffset:218] transition-[stroke-dashoffset] duration-[520ms] ease-svgb04 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" d="M2 26V10A8 8 0 0 1 10 2H166A8 8 0 0 1 174 10V26" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-fg [stroke-width:1.8] [stroke-linecap:butt] [stroke-dasharray:218] [stroke-dashoffset:218] transition-[stroke-dashoffset] duration-[520ms] ease-svgb04 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" d="M2 26V42A8 8 0 0 0 10 50H166A8 8 0 0 0 174 42V26" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Add to calendar</span>
</button>
<span class="font-svgb04-mono text-[10.5px] tracking-[0.08em] text-svgb04-muted">side seam · 213.1 measured → 218 + 218</span>
</div>
</div>
</div>
</section><section class="svgb-04 relative block w-full min-h-svh overflow-hidden bg-svgb04-bg text-svgb04-fg font-svgb04-sans">
<input class="peer absolute opacity-0 pointer-events-none" type="checkbox" id="svgb-04-theme">
<label class="absolute z-[5] top-4 right-4 grid place-items-center size-11 rounded-full border border-current/20 bg-current/8 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-04-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 grid place-items-center p-[clamp(24px,6vw,72px)]">
<div class="flex flex-col items-center gap-[clamp(12px,2vw,18px)] text-center">
<p class="m-0 font-svgb04-mono text-[11px] tracking-[0.34em] uppercase text-svgb04-stroke">Now showing</p>
<h1 class="m-0 font-svgb04-display text-[clamp(46px,11vw,116px)] leading-[0.88] tracking-[-0.02em] uppercase">The Long<br>Exposure</h1>
<p class="m-0 mb-[clamp(10px,2vw,18px)] text-[13px] tracking-[0.12em] uppercase text-svgb04-muted">70mm · 142 min · Screen 4 · 20:45</p>
<button class="group relative w-[min(300px,92vw)] h-23 flex items-stretch border-0 p-0 bg-svgb04-fg/5 cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb04-stroke focus-visible:outline-offset-[5px] after:content-[''] after:absolute after:inset-x-[42%] after:-bottom-[7px] after:h-0.5 after:bg-svgb04-stroke after:opacity-0 after:scale-x-[0.2] after:[transition:opacity_200ms_ease,transform_320ms_var(--ease-svgb04)] hover:after:opacity-90 hover:after:scale-x-100 hover:after:delay-[620ms] focus-visible:after:opacity-90 focus-visible:after:scale-x-100 focus-visible:after:delay-[620ms] motion-reduce:after:transition-none" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 300 92" aria-hidden="true">
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M150 2H290A8 8 0 0 1 298 10V37A9 9 0 0 0 298 55V82A8 8 0 0 1 290 90H150" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M150 2H10A8 8 0 0 0 2 10V37A9 9 0 0 1 2 55V82A8 8 0 0 0 10 90H150" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4] [stroke-dasharray:3_5] [stroke-linecap:round]" d="M150 12V80" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-stroke [stroke-width:2] [stroke-linecap:butt] [stroke-dasharray:392] [stroke-dashoffset:392] transition-[stroke-dashoffset] duration-[620ms] ease-svgb04 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" d="M150 2H290A8 8 0 0 1 298 10V37A9 9 0 0 0 298 55V82A8 8 0 0 1 290 90H150" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-stroke [stroke-width:2] [stroke-linecap:butt] [stroke-dasharray:392] [stroke-dashoffset:392] transition-[stroke-dashoffset] duration-[620ms] ease-svgb04 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" d="M150 2H10A8 8 0 0 0 2 10V37A9 9 0 0 1 2 55V82A8 8 0 0 0 10 90H150" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1] flex-1 flex flex-col justify-center gap-1">
<span class="font-svgb04-mono text-[9.5px] tracking-[0.2em] uppercase text-svgb04-muted">Seat</span>
<span class="text-[19px] font-bold tracking-[-0.01em] transition-colors duration-300 motion-reduce:transition-none group-hover:text-svgb04-stroke group-focus-visible:text-svgb04-stroke">J-14</span>
</span>
<span class="relative z-[1] flex-[1.4] flex flex-col justify-center gap-1">
<span class="font-svgb04-mono text-[9.5px] tracking-[0.2em] uppercase text-svgb04-muted">Admit one</span>
<span class="text-[19px] font-bold tracking-[-0.01em] transition-colors duration-300 motion-reduce:transition-none group-hover:text-svgb04-stroke group-focus-visible:text-svgb04-stroke">Reserve</span>
</span>
</button>
<div class="flex flex-col items-center gap-2.5 mt-[clamp(14px,3vw,24px)]">
<button class="group relative w-44 h-13 grid place-items-center border-0 p-0 bg-transparent text-[13.5px] font-medium cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb04-stroke focus-visible:outline-offset-4" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 176 52" aria-hidden="true">
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M2 26V10A8 8 0 0 1 10 2H166A8 8 0 0 1 174 10V26" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M2 26V42A8 8 0 0 0 10 50H166A8 8 0 0 0 174 42V26" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-fg [stroke-width:1.8] [stroke-linecap:butt] [stroke-dasharray:218] [stroke-dashoffset:218] transition-[stroke-dashoffset] duration-[520ms] ease-svgb04 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" d="M2 26V10A8 8 0 0 1 10 2H166A8 8 0 0 1 174 10V26" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-fg [stroke-width:1.8] [stroke-linecap:butt] [stroke-dasharray:218] [stroke-dashoffset:218] transition-[stroke-dashoffset] duration-[520ms] ease-svgb04 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" d="M2 26V42A8 8 0 0 0 10 50H166A8 8 0 0 0 174 42V26" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Add to calendar</span>
</button>
<span class="font-svgb04-mono text-[10.5px] tracking-[0.08em] text-svgb04-muted">side seam · 213.1 measured → 218 + 218</span>
</div>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-svgb04-bg: #1b0e0c;
--color-svgb04-bg: oklch(0.20 0.04 30);
--color-svgb04-fg: #f6ece0;
--color-svgb04-fg: oklch(0.95 0.02 70);
--color-svgb04-muted: #b09287;
--color-svgb04-muted: oklch(0.70 0.035 40);
--color-svgb04-rest: rgba(246, 236, 224, 0.2);
--color-svgb04-rest: oklch(0.95 0.02 70 / 0.2);
--color-svgb04-stroke: #ffb84d;
--color-svgb04-stroke: oklch(0.83 0.14 70);
--font-svgb04-sans: 'Manrope', system-ui, sans-serif;
--font-svgb04-display: 'Anton', 'Archivo', system-ui, sans-serif;
--font-svgb04-mono: 'IBM Plex Mono', ui-monospace, monospace;
--ease-svgb04: cubic-bezier(0.33, 1, 0.68, 1);
}
/* Projector haze plus scan lines: two stacked decorative layers, which stay in
CSS because as one arbitrary utility they would be unreadable. */
.svgb-04::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background: radial-gradient(120% 80% at 50% -10%, color-mix(in oklab, var(--color-svgb04-stroke) 16%, transparent), transparent 60%), repeating-linear-gradient(0deg, oklch(0 0 0 / 0.14) 0 1px, transparent 1px 4px);
}
.svgb-04:has(#svgb-04-theme:checked) {
--color-svgb04-bg: #f7efe4;
--color-svgb04-bg: oklch(0.955 0.02 75);
--color-svgb04-fg: #24110d;
--color-svgb04-fg: oklch(0.235 0.045 32);
--color-svgb04-muted: #7a5346;
--color-svgb04-muted: oklch(0.50 0.05 40);
--color-svgb04-rest: rgba(36, 17, 13, 0.24);
--color-svgb04-rest: oklch(0.235 0.045 32 / 0.24);
--color-svgb04-stroke: #b62d0e;
--color-svgb04-stroke: oklch(0.51 0.19 33);
}@import "tailwindcss";
@theme {
--color-svgb04-bg: #1b0e0c;
--color-svgb04-bg: oklch(0.20 0.04 30);
--color-svgb04-fg: #f6ece0;
--color-svgb04-fg: oklch(0.95 0.02 70);
--color-svgb04-muted: #b09287;
--color-svgb04-muted: oklch(0.70 0.035 40);
--color-svgb04-rest: rgba(246, 236, 224, 0.2);
--color-svgb04-rest: oklch(0.95 0.02 70 / 0.2);
--color-svgb04-stroke: #ffb84d;
--color-svgb04-stroke: oklch(0.83 0.14 70);
--font-svgb04-sans: 'Manrope', system-ui, sans-serif;
--font-svgb04-display: 'Anton', 'Archivo', system-ui, sans-serif;
--font-svgb04-mono: 'IBM Plex Mono', ui-monospace, monospace;
--ease-svgb04: cubic-bezier(0.33, 1, 0.68, 1);
}
/* Projector haze plus scan lines: two stacked decorative layers, which stay in
CSS because as one arbitrary utility they would be unreadable. */
.svgb-04::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background: radial-gradient(120% 80% at 50% -10%, color-mix(in oklab, var(--color-svgb04-stroke) 16%, transparent), transparent 60%), repeating-linear-gradient(0deg, oklch(0 0 0 / 0.14) 0 1px, transparent 1px 4px);
}
.svgb-04:has(#svgb-04-theme:checked) {
--color-svgb04-bg: #f7efe4;
--color-svgb04-bg: oklch(0.955 0.02 75);
--color-svgb04-fg: #24110d;
--color-svgb04-fg: oklch(0.235 0.045 32);
--color-svgb04-muted: #7a5346;
--color-svgb04-muted: oklch(0.50 0.05 40);
--color-svgb04-rest: rgba(36, 17, 13, 0.24);
--color-svgb04-rest: oklch(0.235 0.045 32 / 0.24);
--color-svgb04-stroke: #b62d0e;
--color-svgb04-stroke: oklch(0.51 0.19 33);
}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-svgb04-bg: #1b0e0c;
--color-svgb04-bg: oklch(0.20 0.04 30);
--color-svgb04-fg: #f6ece0;
--color-svgb04-fg: oklch(0.95 0.02 70);
--color-svgb04-muted: #b09287;
--color-svgb04-muted: oklch(0.70 0.035 40);
--color-svgb04-rest: rgba(246, 236, 224, 0.2);
--color-svgb04-rest: oklch(0.95 0.02 70 / 0.2);
--color-svgb04-stroke: #ffb84d;
--color-svgb04-stroke: oklch(0.83 0.14 70);
--font-svgb04-sans: 'Manrope', system-ui, sans-serif;
--font-svgb04-display: 'Anton', 'Archivo', system-ui, sans-serif;
--font-svgb04-mono: 'IBM Plex Mono', ui-monospace, monospace;
--ease-svgb04: cubic-bezier(0.33, 1, 0.68, 1);
}
/* Projector haze plus scan lines: two stacked decorative layers, which stay in
CSS because as one arbitrary utility they would be unreadable. */
.svgb-04::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background: radial-gradient(120% 80% at 50% -10%, color-mix(in oklab, var(--color-svgb04-stroke) 16%, transparent), transparent 60%), repeating-linear-gradient(0deg, oklch(0 0 0 / 0.14) 0 1px, transparent 1px 4px);
}
.svgb-04:has(#svgb-04-theme:checked) {
--color-svgb04-bg: #f7efe4;
--color-svgb04-bg: oklch(0.955 0.02 75);
--color-svgb04-fg: #24110d;
--color-svgb04-fg: oklch(0.235 0.045 32);
--color-svgb04-muted: #7a5346;
--color-svgb04-muted: oklch(0.50 0.05 40);
--color-svgb04-rest: rgba(36, 17, 13, 0.24);
--color-svgb04-rest: oklch(0.235 0.045 32 / 0.24);
--color-svgb04-stroke: #b62d0e;
--color-svgb04-stroke: oklch(0.51 0.19 33);
}@import "tailwindcss";
@theme {
--color-svgb04-bg: #1b0e0c;
--color-svgb04-bg: oklch(0.20 0.04 30);
--color-svgb04-fg: #f6ece0;
--color-svgb04-fg: oklch(0.95 0.02 70);
--color-svgb04-muted: #b09287;
--color-svgb04-muted: oklch(0.70 0.035 40);
--color-svgb04-rest: rgba(246, 236, 224, 0.2);
--color-svgb04-rest: oklch(0.95 0.02 70 / 0.2);
--color-svgb04-stroke: #ffb84d;
--color-svgb04-stroke: oklch(0.83 0.14 70);
--font-svgb04-sans: 'Manrope', system-ui, sans-serif;
--font-svgb04-display: 'Anton', 'Archivo', system-ui, sans-serif;
--font-svgb04-mono: 'IBM Plex Mono', ui-monospace, monospace;
--ease-svgb04: cubic-bezier(0.33, 1, 0.68, 1);
}
/* Projector haze plus scan lines: two stacked decorative layers, which stay in
CSS because as one arbitrary utility they would be unreadable. */
.svgb-04::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background: radial-gradient(120% 80% at 50% -10%, color-mix(in oklab, var(--color-svgb04-stroke) 16%, transparent), transparent 60%), repeating-linear-gradient(0deg, oklch(0 0 0 / 0.14) 0 1px, transparent 1px 4px);
}
.svgb-04:has(#svgb-04-theme:checked) {
--color-svgb04-bg: #f7efe4;
--color-svgb04-bg: oklch(0.955 0.02 75);
--color-svgb04-fg: #24110d;
--color-svgb04-fg: oklch(0.235 0.045 32);
--color-svgb04-muted: #7a5346;
--color-svgb04-muted: oklch(0.50 0.05 40);
--color-svgb04-rest: rgba(36, 17, 13, 0.24);
--color-svgb04-rest: oklch(0.235 0.045 32 / 0.24);
--color-svgb04-stroke: #b62d0e;
--color-svgb04-stroke: oklch(0.51 0.19 33);
}<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=Anton&family=Manrope:wght@400;500;700&family=IBM+Plex+Mono:wght@400&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=Anton&family=Manrope:wght@400;500;700&family=IBM+Plex+Mono:wght@400&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. - Both halves carry the same class list, so the shared dash constant is literally shared — the mistake this demo warns about becomes hard to make by accident.
- The seam flash is a pseudo-element built entirely from variants:
after:content-['']throughhover:after:delay-[620ms], where the delay equals the draw duration. - The
after:transition is written as one arbitrary shorthand because opacity and transform need different durations, which theduration-*utility cannot express on its own. - The chip's shorter halves get their own measured constant inline —
[stroke-dasharray:218]against the ticket's 392 — so the two seams stay correct independently. [stroke-linecap:butt]is on the drawn halves and nowhere else; round caps would bulge where the two heads meet.- Fonts are three
--font-*tokens, sofont-svgb04-displayreaches Anton without a families config file.
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 Two Path Split Border Draw Button
Source: https://codefronts.com/components/tailwind-svg-stroke-buttons/tailwind-two-path-split-border-draw-button/
Two halves, one constant — and in Tailwind the constant is literally shared, because both paths carry the same class list character for character. The seam flash is built entirely from pseudo-element variants, from after:content-[''] through hover:after:delay-[620ms], where the delay equals the draw duration so the tear lands exactly as the two heads meet.
## HTML
```html
<section class="svgb-04 relative block w-full min-h-svh overflow-hidden bg-svgb04-bg text-svgb04-fg font-svgb04-sans">
<input class="peer absolute opacity-0 pointer-events-none" type="checkbox" id="svgb-04-theme">
<label class="absolute z-[5] top-4 right-4 grid place-items-center size-11 rounded-full border border-current/20 bg-current/8 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-04-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 grid place-items-center p-[clamp(24px,6vw,72px)]">
<div class="flex flex-col items-center gap-[clamp(12px,2vw,18px)] text-center">
<p class="m-0 font-svgb04-mono text-[11px] tracking-[0.34em] uppercase text-svgb04-stroke">Now showing</p>
<h1 class="m-0 font-svgb04-display text-[clamp(46px,11vw,116px)] leading-[0.88] tracking-[-0.02em] uppercase">The Long<br>Exposure</h1>
<p class="m-0 mb-[clamp(10px,2vw,18px)] text-[13px] tracking-[0.12em] uppercase text-svgb04-muted">70mm · 142 min · Screen 4 · 20:45</p>
<button class="group relative w-[min(300px,92vw)] h-23 flex items-stretch border-0 p-0 bg-svgb04-fg/5 cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb04-stroke focus-visible:outline-offset-[5px] after:content-[''] after:absolute after:inset-x-[42%] after:-bottom-[7px] after:h-0.5 after:bg-svgb04-stroke after:opacity-0 after:scale-x-[0.2] after:[transition:opacity_200ms_ease,transform_320ms_var(--ease-svgb04)] hover:after:opacity-90 hover:after:scale-x-100 hover:after:delay-[620ms] focus-visible:after:opacity-90 focus-visible:after:scale-x-100 focus-visible:after:delay-[620ms] motion-reduce:after:transition-none" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 300 92" aria-hidden="true">
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M150 2H290A8 8 0 0 1 298 10V37A9 9 0 0 0 298 55V82A8 8 0 0 1 290 90H150" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M150 2H10A8 8 0 0 0 2 10V37A9 9 0 0 1 2 55V82A8 8 0 0 0 10 90H150" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4] [stroke-dasharray:3_5] [stroke-linecap:round]" d="M150 12V80" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-stroke [stroke-width:2] [stroke-linecap:butt] [stroke-dasharray:392] [stroke-dashoffset:392] transition-[stroke-dashoffset] duration-[620ms] ease-svgb04 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" d="M150 2H290A8 8 0 0 1 298 10V37A9 9 0 0 0 298 55V82A8 8 0 0 1 290 90H150" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-stroke [stroke-width:2] [stroke-linecap:butt] [stroke-dasharray:392] [stroke-dashoffset:392] transition-[stroke-dashoffset] duration-[620ms] ease-svgb04 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" d="M150 2H10A8 8 0 0 0 2 10V37A9 9 0 0 1 2 55V82A8 8 0 0 0 10 90H150" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1] flex-1 flex flex-col justify-center gap-1">
<span class="font-svgb04-mono text-[9.5px] tracking-[0.2em] uppercase text-svgb04-muted">Seat</span>
<span class="text-[19px] font-bold tracking-[-0.01em] transition-colors duration-300 motion-reduce:transition-none group-hover:text-svgb04-stroke group-focus-visible:text-svgb04-stroke">J-14</span>
</span>
<span class="relative z-[1] flex-[1.4] flex flex-col justify-center gap-1">
<span class="font-svgb04-mono text-[9.5px] tracking-[0.2em] uppercase text-svgb04-muted">Admit one</span>
<span class="text-[19px] font-bold tracking-[-0.01em] transition-colors duration-300 motion-reduce:transition-none group-hover:text-svgb04-stroke group-focus-visible:text-svgb04-stroke">Reserve</span>
</span>
</button>
<div class="flex flex-col items-center gap-2.5 mt-[clamp(14px,3vw,24px)]">
<button class="group relative w-44 h-13 grid place-items-center border-0 p-0 bg-transparent text-[13.5px] font-medium cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb04-stroke focus-visible:outline-offset-4" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 176 52" aria-hidden="true">
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M2 26V10A8 8 0 0 1 10 2H166A8 8 0 0 1 174 10V26" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M2 26V42A8 8 0 0 0 10 50H166A8 8 0 0 0 174 42V26" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-fg [stroke-width:1.8] [stroke-linecap:butt] [stroke-dasharray:218] [stroke-dashoffset:218] transition-[stroke-dashoffset] duration-[520ms] ease-svgb04 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" d="M2 26V10A8 8 0 0 1 10 2H166A8 8 0 0 1 174 10V26" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-fg [stroke-width:1.8] [stroke-linecap:butt] [stroke-dasharray:218] [stroke-dashoffset:218] transition-[stroke-dashoffset] duration-[520ms] ease-svgb04 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" d="M2 26V42A8 8 0 0 0 10 50H166A8 8 0 0 0 174 42V26" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Add to calendar</span>
</button>
<span class="font-svgb04-mono text-[10.5px] tracking-[0.08em] text-svgb04-muted">side seam · 213.1 measured → 218 + 218</span>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-svgb04-bg: #1b0e0c;
--color-svgb04-bg: oklch(0.20 0.04 30);
--color-svgb04-fg: #f6ece0;
--color-svgb04-fg: oklch(0.95 0.02 70);
--color-svgb04-muted: #b09287;
--color-svgb04-muted: oklch(0.70 0.035 40);
--color-svgb04-rest: rgba(246, 236, 224, 0.2);
--color-svgb04-rest: oklch(0.95 0.02 70 / 0.2);
--color-svgb04-stroke: #ffb84d;
--color-svgb04-stroke: oklch(0.83 0.14 70);
--font-svgb04-sans: 'Manrope', system-ui, sans-serif;
--font-svgb04-display: 'Anton', 'Archivo', system-ui, sans-serif;
--font-svgb04-mono: 'IBM Plex Mono', ui-monospace, monospace;
--ease-svgb04: cubic-bezier(0.33, 1, 0.68, 1);
}
/* Projector haze plus scan lines: two stacked decorative layers, which stay in
CSS because as one arbitrary utility they would be unreadable. */
.svgb-04::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background: radial-gradient(120% 80% at 50% -10%, color-mix(in oklab, var(--color-svgb04-stroke) 16%, transparent), transparent 60%), repeating-linear-gradient(0deg, oklch(0 0 0 / 0.14) 0 1px, transparent 1px 4px);
}
.svgb-04:has(#svgb-04-theme:checked) {
--color-svgb04-bg: #f7efe4;
--color-svgb04-bg: oklch(0.955 0.02 75);
--color-svgb04-fg: #24110d;
--color-svgb04-fg: oklch(0.235 0.045 32);
--color-svgb04-muted: #7a5346;
--color-svgb04-muted: oklch(0.50 0.05 40);
--color-svgb04-rest: rgba(36, 17, 13, 0.24);
--color-svgb04-rest: oklch(0.235 0.045 32 / 0.24);
--color-svgb04-stroke: #b62d0e;
--color-svgb04-stroke: oklch(0.51 0.19 33);
}
```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 Two Path Split Border Draw Button
Source: https://codefronts.com/components/tailwind-svg-stroke-buttons/tailwind-two-path-split-border-draw-button/
Two halves, one constant — and in Tailwind the constant is literally shared, because both paths carry the same class list character for character. The seam flash is built entirely from pseudo-element variants, from after:content-[''] through hover:after:delay-[620ms], where the delay equals the draw duration so the tear lands exactly as the two heads meet.
## HTML
```html
<section class="svgb-04 relative block w-full min-h-svh overflow-hidden bg-svgb04-bg text-svgb04-fg font-svgb04-sans">
<input class="peer absolute opacity-0 pointer-events-none" type="checkbox" id="svgb-04-theme">
<label class="absolute z-[5] top-4 right-4 grid place-items-center size-11 rounded-full border border-current/20 bg-current/8 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-04-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 grid place-items-center p-[clamp(24px,6vw,72px)]">
<div class="flex flex-col items-center gap-[clamp(12px,2vw,18px)] text-center">
<p class="m-0 font-svgb04-mono text-[11px] tracking-[0.34em] uppercase text-svgb04-stroke">Now showing</p>
<h1 class="m-0 font-svgb04-display text-[clamp(46px,11vw,116px)] leading-[0.88] tracking-[-0.02em] uppercase">The Long<br>Exposure</h1>
<p class="m-0 mb-[clamp(10px,2vw,18px)] text-[13px] tracking-[0.12em] uppercase text-svgb04-muted">70mm · 142 min · Screen 4 · 20:45</p>
<button class="group relative w-[min(300px,92vw)] h-23 flex items-stretch border-0 p-0 bg-svgb04-fg/5 cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb04-stroke focus-visible:outline-offset-[5px] after:content-[''] after:absolute after:inset-x-[42%] after:-bottom-[7px] after:h-0.5 after:bg-svgb04-stroke after:opacity-0 after:scale-x-[0.2] after:[transition:opacity_200ms_ease,transform_320ms_var(--ease-svgb04)] hover:after:opacity-90 hover:after:scale-x-100 hover:after:delay-[620ms] focus-visible:after:opacity-90 focus-visible:after:scale-x-100 focus-visible:after:delay-[620ms] motion-reduce:after:transition-none" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 300 92" aria-hidden="true">
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M150 2H290A8 8 0 0 1 298 10V37A9 9 0 0 0 298 55V82A8 8 0 0 1 290 90H150" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M150 2H10A8 8 0 0 0 2 10V37A9 9 0 0 1 2 55V82A8 8 0 0 0 10 90H150" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4] [stroke-dasharray:3_5] [stroke-linecap:round]" d="M150 12V80" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-stroke [stroke-width:2] [stroke-linecap:butt] [stroke-dasharray:392] [stroke-dashoffset:392] transition-[stroke-dashoffset] duration-[620ms] ease-svgb04 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" d="M150 2H290A8 8 0 0 1 298 10V37A9 9 0 0 0 298 55V82A8 8 0 0 1 290 90H150" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-stroke [stroke-width:2] [stroke-linecap:butt] [stroke-dasharray:392] [stroke-dashoffset:392] transition-[stroke-dashoffset] duration-[620ms] ease-svgb04 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" d="M150 2H10A8 8 0 0 0 2 10V37A9 9 0 0 1 2 55V82A8 8 0 0 0 10 90H150" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1] flex-1 flex flex-col justify-center gap-1">
<span class="font-svgb04-mono text-[9.5px] tracking-[0.2em] uppercase text-svgb04-muted">Seat</span>
<span class="text-[19px] font-bold tracking-[-0.01em] transition-colors duration-300 motion-reduce:transition-none group-hover:text-svgb04-stroke group-focus-visible:text-svgb04-stroke">J-14</span>
</span>
<span class="relative z-[1] flex-[1.4] flex flex-col justify-center gap-1">
<span class="font-svgb04-mono text-[9.5px] tracking-[0.2em] uppercase text-svgb04-muted">Admit one</span>
<span class="text-[19px] font-bold tracking-[-0.01em] transition-colors duration-300 motion-reduce:transition-none group-hover:text-svgb04-stroke group-focus-visible:text-svgb04-stroke">Reserve</span>
</span>
</button>
<div class="flex flex-col items-center gap-2.5 mt-[clamp(14px,3vw,24px)]">
<button class="group relative w-44 h-13 grid place-items-center border-0 p-0 bg-transparent text-[13.5px] font-medium cursor-pointer focus-visible:outline-2 focus-visible:outline-svgb04-stroke focus-visible:outline-offset-4" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 176 52" aria-hidden="true">
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M2 26V10A8 8 0 0 1 10 2H166A8 8 0 0 1 174 10V26" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-rest [stroke-width:1.4]" d="M2 26V42A8 8 0 0 0 10 50H166A8 8 0 0 0 174 42V26" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-fg [stroke-width:1.8] [stroke-linecap:butt] [stroke-dasharray:218] [stroke-dashoffset:218] transition-[stroke-dashoffset] duration-[520ms] ease-svgb04 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" d="M2 26V10A8 8 0 0 1 10 2H166A8 8 0 0 1 174 10V26" vector-effect="non-scaling-stroke"/>
<path class="fill-none stroke-svgb04-fg [stroke-width:1.8] [stroke-linecap:butt] [stroke-dasharray:218] [stroke-dashoffset:218] transition-[stroke-dashoffset] duration-[520ms] ease-svgb04 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" d="M2 26V42A8 8 0 0 0 10 50H166A8 8 0 0 0 174 42V26" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Add to calendar</span>
</button>
<span class="font-svgb04-mono text-[10.5px] tracking-[0.08em] text-svgb04-muted">side seam · 213.1 measured → 218 + 218</span>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-svgb04-bg: #1b0e0c;
--color-svgb04-bg: oklch(0.20 0.04 30);
--color-svgb04-fg: #f6ece0;
--color-svgb04-fg: oklch(0.95 0.02 70);
--color-svgb04-muted: #b09287;
--color-svgb04-muted: oklch(0.70 0.035 40);
--color-svgb04-rest: rgba(246, 236, 224, 0.2);
--color-svgb04-rest: oklch(0.95 0.02 70 / 0.2);
--color-svgb04-stroke: #ffb84d;
--color-svgb04-stroke: oklch(0.83 0.14 70);
--font-svgb04-sans: 'Manrope', system-ui, sans-serif;
--font-svgb04-display: 'Anton', 'Archivo', system-ui, sans-serif;
--font-svgb04-mono: 'IBM Plex Mono', ui-monospace, monospace;
--ease-svgb04: cubic-bezier(0.33, 1, 0.68, 1);
}
/* Projector haze plus scan lines: two stacked decorative layers, which stay in
CSS because as one arbitrary utility they would be unreadable. */
.svgb-04::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background: radial-gradient(120% 80% at 50% -10%, color-mix(in oklab, var(--color-svgb04-stroke) 16%, transparent), transparent 60%), repeating-linear-gradient(0deg, oklch(0 0 0 / 0.14) 0 1px, transparent 1px 4px);
}
.svgb-04:has(#svgb-04-theme:checked) {
--color-svgb04-bg: #f7efe4;
--color-svgb04-bg: oklch(0.955 0.02 75);
--color-svgb04-fg: #24110d;
--color-svgb04-fg: oklch(0.235 0.045 32);
--color-svgb04-muted: #7a5346;
--color-svgb04-muted: oklch(0.50 0.05 40);
--color-svgb04-rest: rgba(36, 17, 13, 0.24);
--color-svgb04-rest: oklch(0.235 0.045 32 / 0.24);
--color-svgb04-stroke: #b62d0e;
--color-svgb04-stroke: oklch(0.51 0.19 33);
}
```How this works
The shared constant becomes shared markup. Both halves are 392 units, and the CSS version keeps that number in two rules that must agree. Here the halves carry the same string, so a change to one is a change you can see is missing on the other:
class="stroke-svgb04-ink [stroke-width:2] [stroke-linecap:butt]
[stroke-dasharray:392] [stroke-dashoffset:392]
transition-[stroke-dashoffset] duration-[620ms]
group-hover:[stroke-dashoffset:0]"The seam is a pseudo-element written in the markup. Tailwind's before: and after: variants make a decorative element addressable without a stylesheet, including its content:
class="after:content-[''] after:absolute after:inset-y-0
after:opacity-0 hover:after:opacity-100
hover:after:delay-[620ms]"Why that delay is not arbitrary. 620ms is the draw duration. The flash fires as both heads land on the opposite edge, so the tear reads as a consequence of the outline closing rather than a separate animation. When you retime the draw, retime this too — they are the same number typed twice, which is the honest cost of expressing choreography in markup.
One transition that utilities cannot express. The seam fades and lifts on different clocks. transition-[opacity,transform] duration-* would force one duration on both, so the seam declares a full arbitrary shorthand instead. This is the boundary: when two properties need two curves, write the shorthand.
Butt caps, not round. Round caps add half a stroke width at every open end, and these ends meet. On a split border that produces a visible bulge at the seam, so the caps are square and only the drawn halves get the class.
Make it yours
- Retime by editing
duration-[620ms]on both halves andhover:after:delay-[620ms]on the seam; they are the same beat. - Move the tear by changing where the two paths start — the perforation is wherever both
dattributes begin, not a separate element. - Remove the seam flash entirely and the split still reads, because the two heads still arrive together.
- Swap the easing for something sharper if you want the halves to snap into place; keep both halves identical.
- The chip's 218 is its own measurement. Resize it and remeasure, or give it
pathLength="1"and reuse a single constant as demo 11 does.
Gotchas — read before shipping
- Two halves with different constants is the classic bug: one head arrives early and the border closes crooked. Identical class strings are the defence, so resist “tidying” one of them.
after:content-['']is required — a pseudo-element with no content does not render, and Tailwind will not add it for you.- Pseudo-element variants stack in a fixed order: the state comes first, so it is
hover:after:opacity-100, neverafter:hover:opacity-100. duration-*sets one duration for every property in the transition. Two properties needing two clocks means one arbitrary[transition:...], not two utilities.- Round line caps bulge where two heads meet;
[stroke-linecap:butt]matters more here than on a single closed path. - A tear flash that fires before the draw finishes reads as a glitch. Keep the delay equal to the duration, not shorter.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Everything the split needs — two paths, dash constants, pseudo-elements — is universally supported; the floor here is Tailwind v4's own generated sheet. The hand-written CSS version of this demo runs considerably further back, which is worth knowing if the ticket is the only Tailwind on the page.