09 / 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 Focus Visible Stroke Outline for Keyboard Users
The accessibility argument of the collection, expressed as a naming convention: every hover utility here has a group-focus-visible: twin, so typing one and not the other leaves a visible hole in the class list. Focus also thickens the stroke and shortens the draw — a keyboard user is waiting on it, not exploring — and the forced-colors block stays in CSS because system keywords are not theme tokens.
Nucleo: 44,000+ SVG icons in five families · All Icons Bundle $149
Nucleo · Affiliate link
The code
<section class="svgb-09 relative block w-full min-h-svh bg-svgb09-bg text-svgb09-fg font-svgb09-sans">
<input class="peer absolute opacity-0 pointer-events-none" type="checkbox" id="svgb-09-theme">
<label class="absolute z-[5] top-4 right-4 grid place-items-center size-11 rounded-lg border border-current/22 bg-current/6 cursor-pointer transition-[background-color,transform] duration-150 hover:bg-current/13 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-09-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-140 flex flex-col gap-3.5 p-[clamp(22px,4vw,40px)] rounded-[10px] bg-svgb09-card border border-svgb09-rest">
<div class="flex flex-wrap justify-between items-baseline gap-3">
<span class="text-sm font-bold tracking-[0.14em] uppercase">Meridian</span>
<span class="inline-flex items-center gap-2 font-svgb09-mono text-[11px] text-svgb09-muted"><kbd class="font-[inherit] text-[10.5px] px-2 py-1 rounded-[5px] border border-svgb09-rest shadow-[0_1.5px_0_var(--color-svgb09-rest)]">Tab</kbd> walks the order</span>
</div>
<p class="mt-2 mb-0 text-[clamp(34px,6vw,52px)] font-bold tracking-[-0.03em] tabular-nums">£1,480.00</p>
<p class="mt-0 mb-2.5 text-[13.5px] leading-normal text-svgb09-muted [&_strong]:text-svgb09-fg [&_strong]:font-semibold">to <strong>Halden Studio Ltd</strong> · sort 20-45-11 · acct ••••8842</p>
<ol class="list-none m-0 p-0 flex flex-col gap-3">
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">1</span>
<button class="group relative w-[min(340px,100%)] h-15 grid place-items-center border-0 p-0 rounded-lg bg-svgb09-stroke/12 text-[15px] font-semibold cursor-pointer focus:outline-none focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 340 60" aria-hidden="true">
<rect class="fill-none stroke-svgb09-rest [stroke-width:1.4] [stroke-linecap:round]" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
<rect class="fill-none stroke-svgb09-stroke [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:780] [stroke-dashoffset:780] [transition:stroke-dashoffset_560ms_cubic-bezier(0.22,1,0.36,1),stroke-width_160ms_ease] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] group-focus-visible:[stroke-width:2.8] group-focus-visible:[transition-duration:240ms] 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" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Confirm payment</span>
</button>
</li>
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">2</span>
<button class="group relative w-[min(340px,100%)] h-15 grid place-items-center border-0 p-0 rounded-lg bg-svgb09-fg/4 text-[15px] font-medium cursor-pointer focus:outline-none focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 340 60" aria-hidden="true">
<rect class="fill-none stroke-svgb09-rest [stroke-width:1.4] [stroke-linecap:round]" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
<rect class="fill-none stroke-svgb09-stroke [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:780] [stroke-dashoffset:780] [transition:stroke-dashoffset_560ms_cubic-bezier(0.22,1,0.36,1),stroke-width_160ms_ease] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] group-focus-visible:[stroke-width:2.8] group-focus-visible:[transition-duration:240ms] 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" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Schedule for 1 October</span>
</button>
</li>
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">3</span>
<button class="group relative w-[min(340px,100%)] h-15 grid place-items-center border-0 p-0 rounded-lg bg-svgb09-fg/4 text-[15px] font-medium cursor-pointer focus:outline-none focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 340 60" aria-hidden="true">
<rect class="fill-none stroke-svgb09-rest [stroke-width:1.4] [stroke-linecap:round]" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
<rect class="fill-none stroke-svgb09-stroke [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:780] [stroke-dashoffset:780] [transition:stroke-dashoffset_560ms_cubic-bezier(0.22,1,0.36,1),stroke-width_160ms_ease] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] group-focus-visible:[stroke-width:2.8] group-focus-visible:[transition-duration:240ms] 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" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Edit reference</span>
</button>
</li>
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">4</span>
<a class="text-svgb09-stroke text-sm font-medium underline decoration-1 underline-offset-[3px] rounded hover:decoration-2 focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" href="#svgb-09-terms">Payment limits and cut-off times</a>
</li>
</ol>
<div class="flex flex-wrap gap-4 pt-3.5 mt-1 border-t border-svgb09-rest">
<span class="inline-flex items-center gap-2 font-svgb09-mono text-[10.5px] text-svgb09-muted"><span class="w-[22px] h-3 rounded-[3px] border-2 border-svgb09-stroke"></span>hover · 2px stroke only</span>
<span class="inline-flex items-center gap-2 font-svgb09-mono text-[10.5px] text-svgb09-muted"><span class="w-[22px] h-3 rounded-[3px] border-2 border-svgb09-stroke outline-2 outline-svgb09-ring outline-offset-2"></span>focus-visible · outline + stroke</span>
</div>
</div>
</div>
</section><section class="svgb-09 relative block w-full min-h-svh bg-svgb09-bg text-svgb09-fg font-svgb09-sans">
<input class="peer absolute opacity-0 pointer-events-none" type="checkbox" id="svgb-09-theme">
<label class="absolute z-[5] top-4 right-4 grid place-items-center size-11 rounded-lg border border-current/22 bg-current/6 cursor-pointer transition-[background-color,transform] duration-150 hover:bg-current/13 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-09-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-140 flex flex-col gap-3.5 p-[clamp(22px,4vw,40px)] rounded-[10px] bg-svgb09-card border border-svgb09-rest">
<div class="flex flex-wrap justify-between items-baseline gap-3">
<span class="text-sm font-bold tracking-[0.14em] uppercase">Meridian</span>
<span class="inline-flex items-center gap-2 font-svgb09-mono text-[11px] text-svgb09-muted"><kbd class="font-[inherit] text-[10.5px] px-2 py-1 rounded-[5px] border border-svgb09-rest shadow-[0_1.5px_0_var(--color-svgb09-rest)]">Tab</kbd> walks the order</span>
</div>
<p class="mt-2 mb-0 text-[clamp(34px,6vw,52px)] font-bold tracking-[-0.03em] tabular-nums">£1,480.00</p>
<p class="mt-0 mb-2.5 text-[13.5px] leading-normal text-svgb09-muted [&_strong]:text-svgb09-fg [&_strong]:font-semibold">to <strong>Halden Studio Ltd</strong> · sort 20-45-11 · acct ••••8842</p>
<ol class="list-none m-0 p-0 flex flex-col gap-3">
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">1</span>
<button class="group relative w-[min(340px,100%)] h-15 grid place-items-center border-0 p-0 rounded-lg bg-svgb09-stroke/12 text-[15px] font-semibold cursor-pointer focus:outline-none focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 340 60" aria-hidden="true">
<rect class="fill-none stroke-svgb09-rest [stroke-width:1.4] [stroke-linecap:round]" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
<rect class="fill-none stroke-svgb09-stroke [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:780] [stroke-dashoffset:780] [transition:stroke-dashoffset_560ms_cubic-bezier(0.22,1,0.36,1),stroke-width_160ms_ease] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] group-focus-visible:[stroke-width:2.8] group-focus-visible:[transition-duration:240ms] 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" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Confirm payment</span>
</button>
</li>
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">2</span>
<button class="group relative w-[min(340px,100%)] h-15 grid place-items-center border-0 p-0 rounded-lg bg-svgb09-fg/4 text-[15px] font-medium cursor-pointer focus:outline-none focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 340 60" aria-hidden="true">
<rect class="fill-none stroke-svgb09-rest [stroke-width:1.4] [stroke-linecap:round]" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
<rect class="fill-none stroke-svgb09-stroke [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:780] [stroke-dashoffset:780] [transition:stroke-dashoffset_560ms_cubic-bezier(0.22,1,0.36,1),stroke-width_160ms_ease] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] group-focus-visible:[stroke-width:2.8] group-focus-visible:[transition-duration:240ms] 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" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Schedule for 1 October</span>
</button>
</li>
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">3</span>
<button class="group relative w-[min(340px,100%)] h-15 grid place-items-center border-0 p-0 rounded-lg bg-svgb09-fg/4 text-[15px] font-medium cursor-pointer focus:outline-none focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 340 60" aria-hidden="true">
<rect class="fill-none stroke-svgb09-rest [stroke-width:1.4] [stroke-linecap:round]" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
<rect class="fill-none stroke-svgb09-stroke [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:780] [stroke-dashoffset:780] [transition:stroke-dashoffset_560ms_cubic-bezier(0.22,1,0.36,1),stroke-width_160ms_ease] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] group-focus-visible:[stroke-width:2.8] group-focus-visible:[transition-duration:240ms] 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" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Edit reference</span>
</button>
</li>
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">4</span>
<a class="text-svgb09-stroke text-sm font-medium underline decoration-1 underline-offset-[3px] rounded hover:decoration-2 focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" href="#svgb-09-terms">Payment limits and cut-off times</a>
</li>
</ol>
<div class="flex flex-wrap gap-4 pt-3.5 mt-1 border-t border-svgb09-rest">
<span class="inline-flex items-center gap-2 font-svgb09-mono text-[10.5px] text-svgb09-muted"><span class="w-[22px] h-3 rounded-[3px] border-2 border-svgb09-stroke"></span>hover · 2px stroke only</span>
<span class="inline-flex items-center gap-2 font-svgb09-mono text-[10.5px] text-svgb09-muted"><span class="w-[22px] h-3 rounded-[3px] border-2 border-svgb09-stroke outline-2 outline-svgb09-ring outline-offset-2"></span>focus-visible · outline + stroke</span>
</div>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-svgb09-bg: #eceef2;
--color-svgb09-bg: oklch(0.945 0.005 265);
--color-svgb09-fg: #0e1116;
--color-svgb09-fg: oklch(0.175 0.008 265);
--color-svgb09-muted: #545a66;
--color-svgb09-muted: oklch(0.485 0.013 265);
--color-svgb09-card: #ffffff;
--color-svgb09-card: oklch(1 0 0);
--color-svgb09-rest: rgba(14, 17, 22, 0.18);
--color-svgb09-rest: oklch(0.175 0.008 265 / 0.18);
--color-svgb09-stroke: #0b5cd6;
--color-svgb09-stroke: oklch(0.47 0.20 260);
--color-svgb09-ring: #0b0e14;
--color-svgb09-ring: oklch(0.17 0.01 265);
--color-svgb09-ring2: #ffffff;
--color-svgb09-ring2: oklch(1 0 0);
--font-svgb09-sans: 'Public Sans', system-ui, sans-serif;
--font-svgb09-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.svgb-09:has(#svgb-09-theme:checked) {
--color-svgb09-bg: #0c0f14;
--color-svgb09-bg: oklch(0.17 0.008 265);
--color-svgb09-fg: #edeff4;
--color-svgb09-fg: oklch(0.955 0.005 265);
--color-svgb09-muted: #8b91a0;
--color-svgb09-muted: oklch(0.67 0.013 265);
--color-svgb09-card: #14171e;
--color-svgb09-card: oklch(0.215 0.01 265);
--color-svgb09-rest: rgba(237, 239, 244, 0.18);
--color-svgb09-rest: oklch(0.955 0.005 265 / 0.18);
--color-svgb09-stroke: #7fb2ff;
--color-svgb09-stroke: oklch(0.78 0.13 260);
--color-svgb09-ring: #ffffff;
--color-svgb09-ring: oklch(1 0 0);
--color-svgb09-ring2: #00080f;
--color-svgb09-ring2: oklch(0.14 0.02 250);
}
/* Forced-colors mode discards author colours and remaps a small system palette.
An SVG stroke is not part of that palette, so the drawn border has to be
surrendered and a real border put back. There is no utility for this because
the values are system keywords, not tokens. */
@media (forced-colors: active) {
.svgb-09 svg rect {
stroke: transparent;
}
.svgb-09 button {
border: 1px solid ButtonBorder;
}
.svgb-09 button:focus-visible,
.svgb-09 a:focus-visible {
outline: 3px solid Highlight;
outline-offset: 3px;
box-shadow: none;
}
}@import "tailwindcss";
@theme {
--color-svgb09-bg: #eceef2;
--color-svgb09-bg: oklch(0.945 0.005 265);
--color-svgb09-fg: #0e1116;
--color-svgb09-fg: oklch(0.175 0.008 265);
--color-svgb09-muted: #545a66;
--color-svgb09-muted: oklch(0.485 0.013 265);
--color-svgb09-card: #ffffff;
--color-svgb09-card: oklch(1 0 0);
--color-svgb09-rest: rgba(14, 17, 22, 0.18);
--color-svgb09-rest: oklch(0.175 0.008 265 / 0.18);
--color-svgb09-stroke: #0b5cd6;
--color-svgb09-stroke: oklch(0.47 0.20 260);
--color-svgb09-ring: #0b0e14;
--color-svgb09-ring: oklch(0.17 0.01 265);
--color-svgb09-ring2: #ffffff;
--color-svgb09-ring2: oklch(1 0 0);
--font-svgb09-sans: 'Public Sans', system-ui, sans-serif;
--font-svgb09-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.svgb-09:has(#svgb-09-theme:checked) {
--color-svgb09-bg: #0c0f14;
--color-svgb09-bg: oklch(0.17 0.008 265);
--color-svgb09-fg: #edeff4;
--color-svgb09-fg: oklch(0.955 0.005 265);
--color-svgb09-muted: #8b91a0;
--color-svgb09-muted: oklch(0.67 0.013 265);
--color-svgb09-card: #14171e;
--color-svgb09-card: oklch(0.215 0.01 265);
--color-svgb09-rest: rgba(237, 239, 244, 0.18);
--color-svgb09-rest: oklch(0.955 0.005 265 / 0.18);
--color-svgb09-stroke: #7fb2ff;
--color-svgb09-stroke: oklch(0.78 0.13 260);
--color-svgb09-ring: #ffffff;
--color-svgb09-ring: oklch(1 0 0);
--color-svgb09-ring2: #00080f;
--color-svgb09-ring2: oklch(0.14 0.02 250);
}
/* Forced-colors mode discards author colours and remaps a small system palette.
An SVG stroke is not part of that palette, so the drawn border has to be
surrendered and a real border put back. There is no utility for this because
the values are system keywords, not tokens. */
@media (forced-colors: active) {
.svgb-09 svg rect {
stroke: transparent;
}
.svgb-09 button {
border: 1px solid ButtonBorder;
}
.svgb-09 button:focus-visible,
.svgb-09 a:focus-visible {
outline: 3px solid Highlight;
outline-offset: 3px;
box-shadow: none;
}
}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-svgb09-bg: #eceef2;
--color-svgb09-bg: oklch(0.945 0.005 265);
--color-svgb09-fg: #0e1116;
--color-svgb09-fg: oklch(0.175 0.008 265);
--color-svgb09-muted: #545a66;
--color-svgb09-muted: oklch(0.485 0.013 265);
--color-svgb09-card: #ffffff;
--color-svgb09-card: oklch(1 0 0);
--color-svgb09-rest: rgba(14, 17, 22, 0.18);
--color-svgb09-rest: oklch(0.175 0.008 265 / 0.18);
--color-svgb09-stroke: #0b5cd6;
--color-svgb09-stroke: oklch(0.47 0.20 260);
--color-svgb09-ring: #0b0e14;
--color-svgb09-ring: oklch(0.17 0.01 265);
--color-svgb09-ring2: #ffffff;
--color-svgb09-ring2: oklch(1 0 0);
--font-svgb09-sans: 'Public Sans', system-ui, sans-serif;
--font-svgb09-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.svgb-09:has(#svgb-09-theme:checked) {
--color-svgb09-bg: #0c0f14;
--color-svgb09-bg: oklch(0.17 0.008 265);
--color-svgb09-fg: #edeff4;
--color-svgb09-fg: oklch(0.955 0.005 265);
--color-svgb09-muted: #8b91a0;
--color-svgb09-muted: oklch(0.67 0.013 265);
--color-svgb09-card: #14171e;
--color-svgb09-card: oklch(0.215 0.01 265);
--color-svgb09-rest: rgba(237, 239, 244, 0.18);
--color-svgb09-rest: oklch(0.955 0.005 265 / 0.18);
--color-svgb09-stroke: #7fb2ff;
--color-svgb09-stroke: oklch(0.78 0.13 260);
--color-svgb09-ring: #ffffff;
--color-svgb09-ring: oklch(1 0 0);
--color-svgb09-ring2: #00080f;
--color-svgb09-ring2: oklch(0.14 0.02 250);
}
/* Forced-colors mode discards author colours and remaps a small system palette.
An SVG stroke is not part of that palette, so the drawn border has to be
surrendered and a real border put back. There is no utility for this because
the values are system keywords, not tokens. */
@media (forced-colors: active) {
.svgb-09 svg rect {
stroke: transparent;
}
.svgb-09 button {
border: 1px solid ButtonBorder;
}
.svgb-09 button:focus-visible,
.svgb-09 a:focus-visible {
outline: 3px solid Highlight;
outline-offset: 3px;
box-shadow: none;
}
}@import "tailwindcss";
@theme {
--color-svgb09-bg: #eceef2;
--color-svgb09-bg: oklch(0.945 0.005 265);
--color-svgb09-fg: #0e1116;
--color-svgb09-fg: oklch(0.175 0.008 265);
--color-svgb09-muted: #545a66;
--color-svgb09-muted: oklch(0.485 0.013 265);
--color-svgb09-card: #ffffff;
--color-svgb09-card: oklch(1 0 0);
--color-svgb09-rest: rgba(14, 17, 22, 0.18);
--color-svgb09-rest: oklch(0.175 0.008 265 / 0.18);
--color-svgb09-stroke: #0b5cd6;
--color-svgb09-stroke: oklch(0.47 0.20 260);
--color-svgb09-ring: #0b0e14;
--color-svgb09-ring: oklch(0.17 0.01 265);
--color-svgb09-ring2: #ffffff;
--color-svgb09-ring2: oklch(1 0 0);
--font-svgb09-sans: 'Public Sans', system-ui, sans-serif;
--font-svgb09-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.svgb-09:has(#svgb-09-theme:checked) {
--color-svgb09-bg: #0c0f14;
--color-svgb09-bg: oklch(0.17 0.008 265);
--color-svgb09-fg: #edeff4;
--color-svgb09-fg: oklch(0.955 0.005 265);
--color-svgb09-muted: #8b91a0;
--color-svgb09-muted: oklch(0.67 0.013 265);
--color-svgb09-card: #14171e;
--color-svgb09-card: oklch(0.215 0.01 265);
--color-svgb09-rest: rgba(237, 239, 244, 0.18);
--color-svgb09-rest: oklch(0.955 0.005 265 / 0.18);
--color-svgb09-stroke: #7fb2ff;
--color-svgb09-stroke: oklch(0.78 0.13 260);
--color-svgb09-ring: #ffffff;
--color-svgb09-ring: oklch(1 0 0);
--color-svgb09-ring2: #00080f;
--color-svgb09-ring2: oklch(0.14 0.02 250);
}
/* Forced-colors mode discards author colours and remaps a small system palette.
An SVG stroke is not part of that palette, so the drawn border has to be
surrendered and a real border put back. There is no utility for this because
the values are system keywords, not tokens. */
@media (forced-colors: active) {
.svgb-09 svg rect {
stroke: transparent;
}
.svgb-09 button {
border: 1px solid ButtonBorder;
}
.svgb-09 button:focus-visible,
.svgb-09 a:focus-visible {
outline: 3px solid Highlight;
outline-offset: 3px;
box-shadow: none;
}
}<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=Public+Sans:wght@400;500;700&family=JetBrains+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=Public+Sans:wght@400;500;700&family=JetBrains+Mono:wght@400;500&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The webfonts load as
<link>tags 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. - Every hover utility has a
group-focus-visible:twin, which is the demo’s whole argument expressed as a naming convention: if you type one and not the other, the omission is visible in the markup. focus:outline-nonepaired withfocus-visible:outline-2reproduces:focus:not(:focus-visible)— Tailwind ordersfocus-visibleafterfocus, so a mouse click gets no ring and a Tab key does.- The keyboard ring is deliberately two-part:
focus-visible:outline-svgb09-ringplusfocus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)], so it stays visible on both the card and the primary button’s tinted fill. - Focus also thickens the stroke —
group-focus-visible:[stroke-width:2.8]— and shortens the draw withgroup-focus-visible:[transition-duration:240ms], because a keyboard user is waiting on it, not exploring. - The forced-colors block stays in CSS. Its values are system keywords like
ButtonBorderandHighlight, which are not theme tokens and must not be. - The two legend swatches are pure utilities — a border for hover, a border plus an outline for focus — so the key matches the real thing by construction.
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 Focus Visible Stroke Outline for Keyboard Users
Source: https://codefronts.com/components/tailwind-svg-stroke-buttons/tailwind-focus-visible-stroke-outline-for-keyboard-users/
The accessibility argument of the collection, expressed as a naming convention: every hover utility here has a group-focus-visible: twin, so typing one and not the other leaves a visible hole in the class list. Focus also thickens the stroke and shortens the draw — a keyboard user is waiting on it, not exploring — and the forced-colors block stays in CSS because system keywords are not theme tokens.
## HTML
```html
<section class="svgb-09 relative block w-full min-h-svh bg-svgb09-bg text-svgb09-fg font-svgb09-sans">
<input class="peer absolute opacity-0 pointer-events-none" type="checkbox" id="svgb-09-theme">
<label class="absolute z-[5] top-4 right-4 grid place-items-center size-11 rounded-lg border border-current/22 bg-current/6 cursor-pointer transition-[background-color,transform] duration-150 hover:bg-current/13 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-09-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-140 flex flex-col gap-3.5 p-[clamp(22px,4vw,40px)] rounded-[10px] bg-svgb09-card border border-svgb09-rest">
<div class="flex flex-wrap justify-between items-baseline gap-3">
<span class="text-sm font-bold tracking-[0.14em] uppercase">Meridian</span>
<span class="inline-flex items-center gap-2 font-svgb09-mono text-[11px] text-svgb09-muted"><kbd class="font-[inherit] text-[10.5px] px-2 py-1 rounded-[5px] border border-svgb09-rest shadow-[0_1.5px_0_var(--color-svgb09-rest)]">Tab</kbd> walks the order</span>
</div>
<p class="mt-2 mb-0 text-[clamp(34px,6vw,52px)] font-bold tracking-[-0.03em] tabular-nums">£1,480.00</p>
<p class="mt-0 mb-2.5 text-[13.5px] leading-normal text-svgb09-muted [&_strong]:text-svgb09-fg [&_strong]:font-semibold">to <strong>Halden Studio Ltd</strong> · sort 20-45-11 · acct ••••8842</p>
<ol class="list-none m-0 p-0 flex flex-col gap-3">
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">1</span>
<button class="group relative w-[min(340px,100%)] h-15 grid place-items-center border-0 p-0 rounded-lg bg-svgb09-stroke/12 text-[15px] font-semibold cursor-pointer focus:outline-none focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 340 60" aria-hidden="true">
<rect class="fill-none stroke-svgb09-rest [stroke-width:1.4] [stroke-linecap:round]" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
<rect class="fill-none stroke-svgb09-stroke [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:780] [stroke-dashoffset:780] [transition:stroke-dashoffset_560ms_cubic-bezier(0.22,1,0.36,1),stroke-width_160ms_ease] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] group-focus-visible:[stroke-width:2.8] group-focus-visible:[transition-duration:240ms] 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" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Confirm payment</span>
</button>
</li>
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">2</span>
<button class="group relative w-[min(340px,100%)] h-15 grid place-items-center border-0 p-0 rounded-lg bg-svgb09-fg/4 text-[15px] font-medium cursor-pointer focus:outline-none focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 340 60" aria-hidden="true">
<rect class="fill-none stroke-svgb09-rest [stroke-width:1.4] [stroke-linecap:round]" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
<rect class="fill-none stroke-svgb09-stroke [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:780] [stroke-dashoffset:780] [transition:stroke-dashoffset_560ms_cubic-bezier(0.22,1,0.36,1),stroke-width_160ms_ease] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] group-focus-visible:[stroke-width:2.8] group-focus-visible:[transition-duration:240ms] 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" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Schedule for 1 October</span>
</button>
</li>
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">3</span>
<button class="group relative w-[min(340px,100%)] h-15 grid place-items-center border-0 p-0 rounded-lg bg-svgb09-fg/4 text-[15px] font-medium cursor-pointer focus:outline-none focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 340 60" aria-hidden="true">
<rect class="fill-none stroke-svgb09-rest [stroke-width:1.4] [stroke-linecap:round]" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
<rect class="fill-none stroke-svgb09-stroke [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:780] [stroke-dashoffset:780] [transition:stroke-dashoffset_560ms_cubic-bezier(0.22,1,0.36,1),stroke-width_160ms_ease] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] group-focus-visible:[stroke-width:2.8] group-focus-visible:[transition-duration:240ms] 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" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Edit reference</span>
</button>
</li>
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">4</span>
<a class="text-svgb09-stroke text-sm font-medium underline decoration-1 underline-offset-[3px] rounded hover:decoration-2 focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" href="#svgb-09-terms">Payment limits and cut-off times</a>
</li>
</ol>
<div class="flex flex-wrap gap-4 pt-3.5 mt-1 border-t border-svgb09-rest">
<span class="inline-flex items-center gap-2 font-svgb09-mono text-[10.5px] text-svgb09-muted"><span class="w-[22px] h-3 rounded-[3px] border-2 border-svgb09-stroke"></span>hover · 2px stroke only</span>
<span class="inline-flex items-center gap-2 font-svgb09-mono text-[10.5px] text-svgb09-muted"><span class="w-[22px] h-3 rounded-[3px] border-2 border-svgb09-stroke outline-2 outline-svgb09-ring outline-offset-2"></span>focus-visible · outline + stroke</span>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-svgb09-bg: #eceef2;
--color-svgb09-bg: oklch(0.945 0.005 265);
--color-svgb09-fg: #0e1116;
--color-svgb09-fg: oklch(0.175 0.008 265);
--color-svgb09-muted: #545a66;
--color-svgb09-muted: oklch(0.485 0.013 265);
--color-svgb09-card: #ffffff;
--color-svgb09-card: oklch(1 0 0);
--color-svgb09-rest: rgba(14, 17, 22, 0.18);
--color-svgb09-rest: oklch(0.175 0.008 265 / 0.18);
--color-svgb09-stroke: #0b5cd6;
--color-svgb09-stroke: oklch(0.47 0.20 260);
--color-svgb09-ring: #0b0e14;
--color-svgb09-ring: oklch(0.17 0.01 265);
--color-svgb09-ring2: #ffffff;
--color-svgb09-ring2: oklch(1 0 0);
--font-svgb09-sans: 'Public Sans', system-ui, sans-serif;
--font-svgb09-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.svgb-09:has(#svgb-09-theme:checked) {
--color-svgb09-bg: #0c0f14;
--color-svgb09-bg: oklch(0.17 0.008 265);
--color-svgb09-fg: #edeff4;
--color-svgb09-fg: oklch(0.955 0.005 265);
--color-svgb09-muted: #8b91a0;
--color-svgb09-muted: oklch(0.67 0.013 265);
--color-svgb09-card: #14171e;
--color-svgb09-card: oklch(0.215 0.01 265);
--color-svgb09-rest: rgba(237, 239, 244, 0.18);
--color-svgb09-rest: oklch(0.955 0.005 265 / 0.18);
--color-svgb09-stroke: #7fb2ff;
--color-svgb09-stroke: oklch(0.78 0.13 260);
--color-svgb09-ring: #ffffff;
--color-svgb09-ring: oklch(1 0 0);
--color-svgb09-ring2: #00080f;
--color-svgb09-ring2: oklch(0.14 0.02 250);
}
/* Forced-colors mode discards author colours and remaps a small system palette.
An SVG stroke is not part of that palette, so the drawn border has to be
surrendered and a real border put back. There is no utility for this because
the values are system keywords, not tokens. */
@media (forced-colors: active) {
.svgb-09 svg rect {
stroke: transparent;
}
.svgb-09 button {
border: 1px solid ButtonBorder;
}
.svgb-09 button:focus-visible,
.svgb-09 a:focus-visible {
outline: 3px solid Highlight;
outline-offset: 3px;
box-shadow: none;
}
}
```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 Focus Visible Stroke Outline for Keyboard Users
Source: https://codefronts.com/components/tailwind-svg-stroke-buttons/tailwind-focus-visible-stroke-outline-for-keyboard-users/
The accessibility argument of the collection, expressed as a naming convention: every hover utility here has a group-focus-visible: twin, so typing one and not the other leaves a visible hole in the class list. Focus also thickens the stroke and shortens the draw — a keyboard user is waiting on it, not exploring — and the forced-colors block stays in CSS because system keywords are not theme tokens.
## HTML
```html
<section class="svgb-09 relative block w-full min-h-svh bg-svgb09-bg text-svgb09-fg font-svgb09-sans">
<input class="peer absolute opacity-0 pointer-events-none" type="checkbox" id="svgb-09-theme">
<label class="absolute z-[5] top-4 right-4 grid place-items-center size-11 rounded-lg border border-current/22 bg-current/6 cursor-pointer transition-[background-color,transform] duration-150 hover:bg-current/13 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-09-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-140 flex flex-col gap-3.5 p-[clamp(22px,4vw,40px)] rounded-[10px] bg-svgb09-card border border-svgb09-rest">
<div class="flex flex-wrap justify-between items-baseline gap-3">
<span class="text-sm font-bold tracking-[0.14em] uppercase">Meridian</span>
<span class="inline-flex items-center gap-2 font-svgb09-mono text-[11px] text-svgb09-muted"><kbd class="font-[inherit] text-[10.5px] px-2 py-1 rounded-[5px] border border-svgb09-rest shadow-[0_1.5px_0_var(--color-svgb09-rest)]">Tab</kbd> walks the order</span>
</div>
<p class="mt-2 mb-0 text-[clamp(34px,6vw,52px)] font-bold tracking-[-0.03em] tabular-nums">£1,480.00</p>
<p class="mt-0 mb-2.5 text-[13.5px] leading-normal text-svgb09-muted [&_strong]:text-svgb09-fg [&_strong]:font-semibold">to <strong>Halden Studio Ltd</strong> · sort 20-45-11 · acct ••••8842</p>
<ol class="list-none m-0 p-0 flex flex-col gap-3">
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">1</span>
<button class="group relative w-[min(340px,100%)] h-15 grid place-items-center border-0 p-0 rounded-lg bg-svgb09-stroke/12 text-[15px] font-semibold cursor-pointer focus:outline-none focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 340 60" aria-hidden="true">
<rect class="fill-none stroke-svgb09-rest [stroke-width:1.4] [stroke-linecap:round]" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
<rect class="fill-none stroke-svgb09-stroke [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:780] [stroke-dashoffset:780] [transition:stroke-dashoffset_560ms_cubic-bezier(0.22,1,0.36,1),stroke-width_160ms_ease] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] group-focus-visible:[stroke-width:2.8] group-focus-visible:[transition-duration:240ms] 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" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Confirm payment</span>
</button>
</li>
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">2</span>
<button class="group relative w-[min(340px,100%)] h-15 grid place-items-center border-0 p-0 rounded-lg bg-svgb09-fg/4 text-[15px] font-medium cursor-pointer focus:outline-none focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 340 60" aria-hidden="true">
<rect class="fill-none stroke-svgb09-rest [stroke-width:1.4] [stroke-linecap:round]" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
<rect class="fill-none stroke-svgb09-stroke [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:780] [stroke-dashoffset:780] [transition:stroke-dashoffset_560ms_cubic-bezier(0.22,1,0.36,1),stroke-width_160ms_ease] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] group-focus-visible:[stroke-width:2.8] group-focus-visible:[transition-duration:240ms] 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" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Schedule for 1 October</span>
</button>
</li>
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">3</span>
<button class="group relative w-[min(340px,100%)] h-15 grid place-items-center border-0 p-0 rounded-lg bg-svgb09-fg/4 text-[15px] font-medium cursor-pointer focus:outline-none focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" type="button">
<svg class="absolute inset-0 w-full h-full overflow-visible" viewBox="0 0 340 60" aria-hidden="true">
<rect class="fill-none stroke-svgb09-rest [stroke-width:1.4] [stroke-linecap:round]" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
<rect class="fill-none stroke-svgb09-stroke [stroke-width:2] [stroke-linecap:round] [stroke-dasharray:780] [stroke-dashoffset:780] [transition:stroke-dashoffset_560ms_cubic-bezier(0.22,1,0.36,1),stroke-width_160ms_ease] group-hover:[stroke-dashoffset:0] group-focus-visible:[stroke-dashoffset:0] group-focus-visible:[stroke-width:2.8] group-focus-visible:[transition-duration:240ms] 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" x="1.5" y="1.5" width="337" height="57" rx="8" vector-effect="non-scaling-stroke"/>
</svg>
<span class="relative z-[1]">Edit reference</span>
</button>
</li>
<li class="flex items-center gap-3.5">
<span class="flex-none size-[22px] grid place-items-center rounded-full border border-svgb09-rest font-svgb09-mono text-[10.5px] text-svgb09-muted" aria-hidden="true">4</span>
<a class="text-svgb09-stroke text-sm font-medium underline decoration-1 underline-offset-[3px] rounded hover:decoration-2 focus-visible:outline-2 focus-visible:outline-svgb09-ring focus-visible:outline-offset-[3px] focus-visible:shadow-[0_0_0_5px_var(--color-svgb09-ring2)]" href="#svgb-09-terms">Payment limits and cut-off times</a>
</li>
</ol>
<div class="flex flex-wrap gap-4 pt-3.5 mt-1 border-t border-svgb09-rest">
<span class="inline-flex items-center gap-2 font-svgb09-mono text-[10.5px] text-svgb09-muted"><span class="w-[22px] h-3 rounded-[3px] border-2 border-svgb09-stroke"></span>hover · 2px stroke only</span>
<span class="inline-flex items-center gap-2 font-svgb09-mono text-[10.5px] text-svgb09-muted"><span class="w-[22px] h-3 rounded-[3px] border-2 border-svgb09-stroke outline-2 outline-svgb09-ring outline-offset-2"></span>focus-visible · outline + stroke</span>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-svgb09-bg: #eceef2;
--color-svgb09-bg: oklch(0.945 0.005 265);
--color-svgb09-fg: #0e1116;
--color-svgb09-fg: oklch(0.175 0.008 265);
--color-svgb09-muted: #545a66;
--color-svgb09-muted: oklch(0.485 0.013 265);
--color-svgb09-card: #ffffff;
--color-svgb09-card: oklch(1 0 0);
--color-svgb09-rest: rgba(14, 17, 22, 0.18);
--color-svgb09-rest: oklch(0.175 0.008 265 / 0.18);
--color-svgb09-stroke: #0b5cd6;
--color-svgb09-stroke: oklch(0.47 0.20 260);
--color-svgb09-ring: #0b0e14;
--color-svgb09-ring: oklch(0.17 0.01 265);
--color-svgb09-ring2: #ffffff;
--color-svgb09-ring2: oklch(1 0 0);
--font-svgb09-sans: 'Public Sans', system-ui, sans-serif;
--font-svgb09-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.svgb-09:has(#svgb-09-theme:checked) {
--color-svgb09-bg: #0c0f14;
--color-svgb09-bg: oklch(0.17 0.008 265);
--color-svgb09-fg: #edeff4;
--color-svgb09-fg: oklch(0.955 0.005 265);
--color-svgb09-muted: #8b91a0;
--color-svgb09-muted: oklch(0.67 0.013 265);
--color-svgb09-card: #14171e;
--color-svgb09-card: oklch(0.215 0.01 265);
--color-svgb09-rest: rgba(237, 239, 244, 0.18);
--color-svgb09-rest: oklch(0.955 0.005 265 / 0.18);
--color-svgb09-stroke: #7fb2ff;
--color-svgb09-stroke: oklch(0.78 0.13 260);
--color-svgb09-ring: #ffffff;
--color-svgb09-ring: oklch(1 0 0);
--color-svgb09-ring2: #00080f;
--color-svgb09-ring2: oklch(0.14 0.02 250);
}
/* Forced-colors mode discards author colours and remaps a small system palette.
An SVG stroke is not part of that palette, so the drawn border has to be
surrendered and a real border put back. There is no utility for this because
the values are system keywords, not tokens. */
@media (forced-colors: active) {
.svgb-09 svg rect {
stroke: transparent;
}
.svgb-09 button {
border: 1px solid ButtonBorder;
}
.svgb-09 button:focus-visible,
.svgb-09 a:focus-visible {
outline: 3px solid Highlight;
outline-offset: 3px;
box-shadow: none;
}
}
```How this works
The pairing is the pattern. A stroke that only draws on hover is invisible to anyone driving the page from a keyboard. Written as utilities, the fix is a habit you can see:
class="group-hover:[stroke-dashoffset:0]
group-focus-visible:[stroke-dashoffset:0]
group-hover:text-svgb09-ink
group-focus-visible:text-svgb09-ink"Mouse clicks should not get a ring; Tab should. focus:outline-none focus-visible:outline-2 is the utility form of :focus:not(:focus-visible). It works because Tailwind emits focus-visible after focus, so the later rule wins for keyboard focus while a pointer click keeps the suppressed outline.
The ring is deliberately two-part. One colour cannot stay visible on a white card and on the primary button's tinted fill. An outline in the ring colour plus a 5px shadow in a second colour gives a light-on-dark and dark-on-light edge at once — the same reason marching ants need two colours in demo 06.
Focus is not a slower hover. Under group-focus-visible: the stroke thickens to 2.8 and the draw drops to 240ms. Someone tabbing through a payment form is waiting for confirmation of where they are; someone hovering is browsing. The distinction costs two utilities.
What must stay CSS. The forced-colors block uses ButtonBorder, Highlight and CanvasText. These are system keywords, not design values — putting them in @theme would generate utilities that lie about what they are. SVG strokes are not remapped by forced-colors mode, which is why this block exists at all.
Make it yours
- Retime the focus draw with
group-focus-visible:[transition-duration:...]; keep it shorter than the hover draw. - Recolour the ring by editing the two ring tokens. Test on both the card and the tinted button before shipping — that is what the second colour is for.
- Remove the tab-order badges for production; they are teaching aids, not UI.
- Thicken the focus stroke further on dense layouts, where 2.8 can still get lost against a busy background.
- Keep
focus:outline-nonepaired with afocus-visible:rule. On its own it is one of the most common accessibility regressions on the web.
Gotchas — read before shipping
focus:outline-nonewithout afocus-visible:replacement removes the only signal a keyboard user has. Never ship one without the other.outline-hiddenandoutline-noneare not the same in v4: the former preserves the outline in forced-colors mode, the latter does not.- A single-colour ring will disappear against some backgrounds. Two layers is the reliable answer.
- Forced-colors keywords must not go in
@theme— they are not colours you own, and the generated utilities would be misleading. - SVG strokes are not remapped by forced-colors mode, so a stroke-only state disappears entirely there. Carry state in wording and ARIA too.
- Hover and focus-visible drifting apart is the failure this demo is designed to prevent; if you extract these into a component, extract the pair.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
:focus-visible is supported everywhere current (Safari 15.4+), and forced-colors mode is a Windows high-contrast feature that degrades to being simply absent elsewhere. The floor is Tailwind v4's generated sheet.