37 / 51
All 51 in 51 Tailwind CSS Buttons
- 01Tailwind Submit Button (Form Primary)
- 02Tailwind Primary and Secondary Button Pair
- 03Tailwind Ghost / Outline Button
- 04Tailwind Icon-Only Button with Accessible Tooltip
- 05Tailwind Gradient CTA Button
- 06Tailwind Loading Spinner Button (State Transition)
- 07Tailwind Disabled Button State (Full State Reference)
- 08Tailwind Button Group Component (Segmented Radio-Like)
- 09Tailwind Add to Cart Button
- 10Tailwind Download Progress Button
- 11Tailwind Like Heart Button
- 12Tailwind Confirm Delete Button
- 13Tailwind Play Pause Button
- 14Tailwind Subscribe Confetti Button
- 15Tailwind Magnetic Mercury Ripple Button
- 16Tailwind Brutalist Glitch Button
- 17Tailwind Neon Plasma Burst Button
- 18Tailwind Clay Press Button
- 19Tailwind Editorial Ink Spread Button
- 20Tailwind Particle Shard Burst Button
- 21Tailwind Brushed Brass Plate Button
- 22Tailwind Washi Paper Crane Button
- 23Tailwind Frosted Ice Cube Button
- 24Tailwind Vintage Typewriter Key Button
- 25Tailwind Lacquered Walnut Wood Button
- 26Tailwind Pharma Gel Capsule Button
- 27Tailwind Aged Copper Coin Button
- 28Tailwind Origami Fortune Cookie Button
- 29Tailwind Holographic Foil Sticker Button
- 30Tailwind Brutalist Concrete Slab Button
- 31Tailwind Mercury Bead Morph Button
- 32Tailwind Soft Pill Modern SaaS Button
- 33Tailwind Masking Tape Label Button
- 34Tailwind Obsidian Glass Shard Button
- 35Tailwind Wine Velvet Ribbon Button
- 36Tailwind LED Segment Display Button
- 37Tailwind Cassette Mixtape Label Button
- 38Tailwind Chalkboard Menu Button
- 39Tailwind Vinyl Record Sleeve Ripple Button
- 40Tailwind Polaroid Snapshot Button
- 41Tailwind Airline Boarding Pass Button
- 42Tailwind Aged Brass Subway Token Button
- 43Tailwind Vintage Rx Pharmacy Label Button
- 44Tailwind Bakelite Radio Dial Toggle Button
- 45Tailwind Regal Postage Stamp Button
- 46Tailwind Victorian Cigar Band Foil Button
- 47Tailwind Vintage Calculator Key Button
- 48Tailwind Toolbox Latch Spotlight Button
- 49Tailwind Receipt Paper Total Count-Up Button
- 50Tailwind IBM Punch Card Magnetic Follow Button
- 51Tailwind Brass Periscope Lens Button
Tailwind Cassette Mixtape Label Button
A Type-II cassette with a hand-written label: both reels turn while you hover, and clicking flips the shell to side B. The reels are conic gradients, the spool teeth a repeating conic, and the flip the same transform-3d construction as the coin.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tb-37 w-full min-h-svh grid place-items-center perspective-[1000px] font-tb37-sans p-[clamp(1.5rem,6vw,4rem)]">
<label class="group relative block cursor-pointer w-76 h-46 [-webkit-tap-highlight-color:transparent]">
<input type="checkbox" aria-label="Flip the cassette to side B" class="sr-only">
<span aria-hidden="true" class="relative block size-full transform-3d transition-[transform,translate] duration-[900ms] ease-tb37-out group-hover:-translate-y-[3px] group-active:scale-[0.985] group-has-checked:rotate-y-180 group-has-focus-visible:outline-2 group-has-focus-visible:outline-offset-[6px] group-has-focus-visible:outline-tb37-accent group-has-focus-visible:rounded-[0.55rem] motion-reduce:transition-none">
<span class="tb-37__face absolute inset-0 grid grid-rows-[auto_1fr] gap-3 p-3.5 rounded-[0.55rem] backface-hidden shadow-[inset_0_1px_0_#ffffff33,inset_0_-2px_6px_#00000080,0_18px_30px_-14px_#000000cc]">
<span class="flex items-center justify-between gap-4 px-6 py-2.5 rounded-[0.3rem] bg-[linear-gradient(180deg,#00000080,#00000047)] shadow-[inset_0_0_0_1px_#ffffff14]">
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-play-state:paused] group-hover:[animation-play-state:running] group-has-focus-visible:[animation-play-state:running] motion-reduce:animate-none"></span>
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-duration:4.6s] [animation-play-state:paused] group-hover:[animation-play-state:running] group-has-focus-visible:[animation-play-state:running] motion-reduce:animate-none"></span>
</span>
<span class="tb-37__label relative flex items-end justify-between gap-3 px-3 py-2 rounded-[0.2rem] shadow-[inset_0_0_0_1px_#0000001f]">
<span class="relative z-20 font-tb37-hand text-2xl font-bold text-tb37-ink">summer, mostly</span>
<span class="text-xs font-bold tracking-[0.14em] text-tb37-accent px-1.5 py-0.5 rounded-[0.2rem] bg-tb37-accent-tint">A</span>
</span>
</span>
<span class="tb-37__face absolute inset-0 grid grid-rows-[auto_1fr] gap-3 p-3.5 rounded-[0.55rem] backface-hidden rotate-y-180 shadow-[inset_0_1px_0_#ffffff33,inset_0_-2px_6px_#00000080,0_18px_30px_-14px_#000000cc]">
<span class="flex items-center justify-between gap-4 px-6 py-2.5 rounded-[0.3rem] bg-[linear-gradient(180deg,#00000080,#00000047)] shadow-[inset_0_0_0_1px_#ffffff14]">
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-play-state:paused] group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-duration:4.6s] [animation-play-state:paused] group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
</span>
<span class="tb-37__label relative flex items-end justify-between gap-3 px-3 py-2 rounded-[0.2rem] shadow-[inset_0_0_0_1px_#0000001f]">
<span class="relative z-20 font-tb37-hand text-2xl font-bold text-tb37-ink">the drive home</span>
<span class="text-xs font-bold tracking-[0.14em] text-tb37-accent px-1.5 py-0.5 rounded-[0.2rem] bg-tb37-accent-tint">B</span>
</span>
</span>
</span>
</label>
</div><div class="tb-37 w-full min-h-svh grid place-items-center perspective-[1000px] font-tb37-sans p-[clamp(1.5rem,6vw,4rem)]">
<label class="group relative block cursor-pointer w-76 h-46 [-webkit-tap-highlight-color:transparent]">
<input type="checkbox" aria-label="Flip the cassette to side B" class="sr-only">
<span aria-hidden="true" class="relative block size-full transform-3d transition-[transform,translate] duration-[900ms] ease-tb37-out group-hover:-translate-y-[3px] group-active:scale-[0.985] group-has-checked:rotate-y-180 group-has-focus-visible:outline-2 group-has-focus-visible:outline-offset-[6px] group-has-focus-visible:outline-tb37-accent group-has-focus-visible:rounded-[0.55rem] motion-reduce:transition-none">
<span class="tb-37__face absolute inset-0 grid grid-rows-[auto_1fr] gap-3 p-3.5 rounded-[0.55rem] backface-hidden shadow-[inset_0_1px_0_#ffffff33,inset_0_-2px_6px_#00000080,0_18px_30px_-14px_#000000cc]">
<span class="flex items-center justify-between gap-4 px-6 py-2.5 rounded-[0.3rem] bg-[linear-gradient(180deg,#00000080,#00000047)] shadow-[inset_0_0_0_1px_#ffffff14]">
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-play-state:paused] group-hover:[animation-play-state:running] group-has-focus-visible:[animation-play-state:running] motion-reduce:animate-none"></span>
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-duration:4.6s] [animation-play-state:paused] group-hover:[animation-play-state:running] group-has-focus-visible:[animation-play-state:running] motion-reduce:animate-none"></span>
</span>
<span class="tb-37__label relative flex items-end justify-between gap-3 px-3 py-2 rounded-[0.2rem] shadow-[inset_0_0_0_1px_#0000001f]">
<span class="relative z-20 font-tb37-hand text-2xl font-bold text-tb37-ink">summer, mostly</span>
<span class="text-xs font-bold tracking-[0.14em] text-tb37-accent px-1.5 py-0.5 rounded-[0.2rem] bg-tb37-accent-tint">A</span>
</span>
</span>
<span class="tb-37__face absolute inset-0 grid grid-rows-[auto_1fr] gap-3 p-3.5 rounded-[0.55rem] backface-hidden rotate-y-180 shadow-[inset_0_1px_0_#ffffff33,inset_0_-2px_6px_#00000080,0_18px_30px_-14px_#000000cc]">
<span class="flex items-center justify-between gap-4 px-6 py-2.5 rounded-[0.3rem] bg-[linear-gradient(180deg,#00000080,#00000047)] shadow-[inset_0_0_0_1px_#ffffff14]">
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-play-state:paused] group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-duration:4.6s] [animation-play-state:paused] group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
</span>
<span class="tb-37__label relative flex items-end justify-between gap-3 px-3 py-2 rounded-[0.2rem] shadow-[inset_0_0_0_1px_#0000001f]">
<span class="relative z-20 font-tb37-hand text-2xl font-bold text-tb37-ink">the drive home</span>
<span class="text-xs font-bold tracking-[0.14em] text-tb37-accent px-1.5 py-0.5 rounded-[0.2rem] bg-tb37-accent-tint">B</span>
</span>
</span>
</span>
</label>
</div>@import "tailwindcss";
@theme {
--color-tb37-bg: #181a20;
--color-tb37-bg: oklch(0.2 0.012 275);
--color-tb37-shell: #2b2f38;
--color-tb37-shell: oklch(0.28 0.015 275);
--color-tb37-shell-hi: #3b414d;
--color-tb37-shell-hi: oklch(0.35 0.018 275);
--color-tb37-paper: #f2ead6;
--color-tb37-paper: oklch(0.93 0.03 90);
--color-tb37-ink: #2c2419;
--color-tb37-ink: oklch(0.26 0.025 75);
--color-tb37-hub: #dfe3ea;
--color-tb37-tape: #3a2a1c;
--color-tb37-teeth: #9aa2b0;
--color-tb37-accent: #c2452a;
--color-tb37-accent: oklch(0.53 0.17 32);
--color-tb37-accent-tint: #c2452a29;
--color-tb37-accent-tint: oklch(0.53 0.17 32 / 0.16);
--font-tb37-hand: "Caveat", ui-rounded, cursive;
--font-tb37-sans: "Archivo", ui-sans-serif, system-ui, sans-serif;
--ease-tb37-out: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb37-spin: tb-37-spin 3.4s linear infinite;
}
@keyframes tb-37-spin {
to {
rotate: 1turn;
}
}
.tb-37 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(34rem 20rem at 50% 44%, #23262f, transparent 74%), var(--color-tb37-bg);
}
/* PLASTIC SHELL: top-light over a diagonal body. */
.tb-37__face {
background: linear-gradient(180deg, rgba(255,255,255,.12), transparent 34%), linear-gradient(165deg, var(--color-tb37-shell-hi), var(--color-tb37-shell));
}
/* REELS: spool teeth (repeating conic) over hub + wound tape (radial). Four reels
across two faces must be identical, so the recipe lives once. */
.tb-37__reel {
background: repeating-conic-gradient(from 0deg, var(--color-tb37-teeth) 0 8deg, transparent 8deg 30deg), radial-gradient(circle, var(--color-tb37-hub) 30%, var(--color-tb37-tape) 33%, var(--color-tb37-tape) 80%, transparent 81%);
}
/* AGED LABEL + its ruled line. */
.tb-37__label {
background: linear-gradient(180deg, color-mix(in oklab, var(--color-tb37-paper) 92%, white), var(--color-tb37-paper));
}
.tb-37__label::after {
content: '';
position: absolute;
left: .8rem;
right: 3rem;
bottom: .55rem;
height: 1px;
background: color-mix(in oklab, var(--color-tb37-ink) 22%, transparent);
}
@media (prefers-color-scheme: light) {
.tb-37:not([data-theme="dark"]) {
--color-tb37-bg: oklch(0.24 0.015 275);
}
}
.tb-37[data-theme="light"] {
--color-tb37-bg: oklch(0.24 0.015 275);
}
@media (forced-colors: active) {
.tb-37__face {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-37__label {
background: Canvas;
}
.tb-37__label span {
color: CanvasText;
}
}@import "tailwindcss";
@theme {
--color-tb37-bg: #181a20;
--color-tb37-bg: oklch(0.2 0.012 275);
--color-tb37-shell: #2b2f38;
--color-tb37-shell: oklch(0.28 0.015 275);
--color-tb37-shell-hi: #3b414d;
--color-tb37-shell-hi: oklch(0.35 0.018 275);
--color-tb37-paper: #f2ead6;
--color-tb37-paper: oklch(0.93 0.03 90);
--color-tb37-ink: #2c2419;
--color-tb37-ink: oklch(0.26 0.025 75);
--color-tb37-hub: #dfe3ea;
--color-tb37-tape: #3a2a1c;
--color-tb37-teeth: #9aa2b0;
--color-tb37-accent: #c2452a;
--color-tb37-accent: oklch(0.53 0.17 32);
--color-tb37-accent-tint: #c2452a29;
--color-tb37-accent-tint: oklch(0.53 0.17 32 / 0.16);
--font-tb37-hand: "Caveat", ui-rounded, cursive;
--font-tb37-sans: "Archivo", ui-sans-serif, system-ui, sans-serif;
--ease-tb37-out: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb37-spin: tb-37-spin 3.4s linear infinite;
}
@keyframes tb-37-spin {
to {
rotate: 1turn;
}
}
.tb-37 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(34rem 20rem at 50% 44%, #23262f, transparent 74%), var(--color-tb37-bg);
}
/* PLASTIC SHELL: top-light over a diagonal body. */
.tb-37__face {
background: linear-gradient(180deg, rgba(255,255,255,.12), transparent 34%), linear-gradient(165deg, var(--color-tb37-shell-hi), var(--color-tb37-shell));
}
/* REELS: spool teeth (repeating conic) over hub + wound tape (radial). Four reels
across two faces must be identical, so the recipe lives once. */
.tb-37__reel {
background: repeating-conic-gradient(from 0deg, var(--color-tb37-teeth) 0 8deg, transparent 8deg 30deg), radial-gradient(circle, var(--color-tb37-hub) 30%, var(--color-tb37-tape) 33%, var(--color-tb37-tape) 80%, transparent 81%);
}
/* AGED LABEL + its ruled line. */
.tb-37__label {
background: linear-gradient(180deg, color-mix(in oklab, var(--color-tb37-paper) 92%, white), var(--color-tb37-paper));
}
.tb-37__label::after {
content: '';
position: absolute;
left: .8rem;
right: 3rem;
bottom: .55rem;
height: 1px;
background: color-mix(in oklab, var(--color-tb37-ink) 22%, transparent);
}
@media (prefers-color-scheme: light) {
.tb-37:not([data-theme="dark"]) {
--color-tb37-bg: oklch(0.24 0.015 275);
}
}
.tb-37[data-theme="light"] {
--color-tb37-bg: oklch(0.24 0.015 275);
}
@media (forced-colors: active) {
.tb-37__face {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-37__label {
background: Canvas;
}
.tb-37__label span {
color: CanvasText;
}
}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-tb37-bg: #181a20;
--color-tb37-bg: oklch(0.2 0.012 275);
--color-tb37-shell: #2b2f38;
--color-tb37-shell: oklch(0.28 0.015 275);
--color-tb37-shell-hi: #3b414d;
--color-tb37-shell-hi: oklch(0.35 0.018 275);
--color-tb37-paper: #f2ead6;
--color-tb37-paper: oklch(0.93 0.03 90);
--color-tb37-ink: #2c2419;
--color-tb37-ink: oklch(0.26 0.025 75);
--color-tb37-hub: #dfe3ea;
--color-tb37-tape: #3a2a1c;
--color-tb37-teeth: #9aa2b0;
--color-tb37-accent: #c2452a;
--color-tb37-accent: oklch(0.53 0.17 32);
--color-tb37-accent-tint: #c2452a29;
--color-tb37-accent-tint: oklch(0.53 0.17 32 / 0.16);
--font-tb37-hand: "Caveat", ui-rounded, cursive;
--font-tb37-sans: "Archivo", ui-sans-serif, system-ui, sans-serif;
--ease-tb37-out: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb37-spin: tb-37-spin 3.4s linear infinite;
}
@keyframes tb-37-spin {
to {
rotate: 1turn;
}
}
.tb-37 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(34rem 20rem at 50% 44%, #23262f, transparent 74%), var(--color-tb37-bg);
}
/* PLASTIC SHELL: top-light over a diagonal body. */
.tb-37__face {
background: linear-gradient(180deg, rgba(255,255,255,.12), transparent 34%), linear-gradient(165deg, var(--color-tb37-shell-hi), var(--color-tb37-shell));
}
/* REELS: spool teeth (repeating conic) over hub + wound tape (radial). Four reels
across two faces must be identical, so the recipe lives once. */
.tb-37__reel {
background: repeating-conic-gradient(from 0deg, var(--color-tb37-teeth) 0 8deg, transparent 8deg 30deg), radial-gradient(circle, var(--color-tb37-hub) 30%, var(--color-tb37-tape) 33%, var(--color-tb37-tape) 80%, transparent 81%);
}
/* AGED LABEL + its ruled line. */
.tb-37__label {
background: linear-gradient(180deg, color-mix(in oklab, var(--color-tb37-paper) 92%, white), var(--color-tb37-paper));
}
.tb-37__label::after {
content: '';
position: absolute;
left: .8rem;
right: 3rem;
bottom: .55rem;
height: 1px;
background: color-mix(in oklab, var(--color-tb37-ink) 22%, transparent);
}
@media (prefers-color-scheme: light) {
.tb-37:not([data-theme="dark"]) {
--color-tb37-bg: oklch(0.24 0.015 275);
}
}
.tb-37[data-theme="light"] {
--color-tb37-bg: oklch(0.24 0.015 275);
}
@media (forced-colors: active) {
.tb-37__face {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-37__label {
background: Canvas;
}
.tb-37__label span {
color: CanvasText;
}
}@import "tailwindcss";
@theme {
--color-tb37-bg: #181a20;
--color-tb37-bg: oklch(0.2 0.012 275);
--color-tb37-shell: #2b2f38;
--color-tb37-shell: oklch(0.28 0.015 275);
--color-tb37-shell-hi: #3b414d;
--color-tb37-shell-hi: oklch(0.35 0.018 275);
--color-tb37-paper: #f2ead6;
--color-tb37-paper: oklch(0.93 0.03 90);
--color-tb37-ink: #2c2419;
--color-tb37-ink: oklch(0.26 0.025 75);
--color-tb37-hub: #dfe3ea;
--color-tb37-tape: #3a2a1c;
--color-tb37-teeth: #9aa2b0;
--color-tb37-accent: #c2452a;
--color-tb37-accent: oklch(0.53 0.17 32);
--color-tb37-accent-tint: #c2452a29;
--color-tb37-accent-tint: oklch(0.53 0.17 32 / 0.16);
--font-tb37-hand: "Caveat", ui-rounded, cursive;
--font-tb37-sans: "Archivo", ui-sans-serif, system-ui, sans-serif;
--ease-tb37-out: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb37-spin: tb-37-spin 3.4s linear infinite;
}
@keyframes tb-37-spin {
to {
rotate: 1turn;
}
}
.tb-37 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(34rem 20rem at 50% 44%, #23262f, transparent 74%), var(--color-tb37-bg);
}
/* PLASTIC SHELL: top-light over a diagonal body. */
.tb-37__face {
background: linear-gradient(180deg, rgba(255,255,255,.12), transparent 34%), linear-gradient(165deg, var(--color-tb37-shell-hi), var(--color-tb37-shell));
}
/* REELS: spool teeth (repeating conic) over hub + wound tape (radial). Four reels
across two faces must be identical, so the recipe lives once. */
.tb-37__reel {
background: repeating-conic-gradient(from 0deg, var(--color-tb37-teeth) 0 8deg, transparent 8deg 30deg), radial-gradient(circle, var(--color-tb37-hub) 30%, var(--color-tb37-tape) 33%, var(--color-tb37-tape) 80%, transparent 81%);
}
/* AGED LABEL + its ruled line. */
.tb-37__label {
background: linear-gradient(180deg, color-mix(in oklab, var(--color-tb37-paper) 92%, white), var(--color-tb37-paper));
}
.tb-37__label::after {
content: '';
position: absolute;
left: .8rem;
right: 3rem;
bottom: .55rem;
height: 1px;
background: color-mix(in oklab, var(--color-tb37-ink) 22%, transparent);
}
@media (prefers-color-scheme: light) {
.tb-37:not([data-theme="dark"]) {
--color-tb37-bg: oklch(0.24 0.015 275);
}
}
.tb-37[data-theme="light"] {
--color-tb37-bg: oklch(0.24 0.015 275);
}
@media (forced-colors: active) {
.tb-37__face {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-37__label {
background: Canvas;
}
.tb-37__label span {
color: CanvasText;
}
}<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=Caveat:wght@600;700&family=Archivo:wght@600;700&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=Caveat:wght@600;700&family=Archivo:wght@600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Utilities: the flip, both reels' animation and their play-state toggle, the phase offset, the window, the label layout, every state. CSS: the shell gradient, the reel recipe, the label paper and its ruled line.
[animation-play-state:paused]plusgroup-hover:[animation-play-state:running]is the port's most useful small discovery: v4 lets a variant override a single animation longhand, so "resume" is expressible without a second animation or any JS.[animation-duration:4.6s]on the second reel only is the same mechanism used for character rather than control — the two hubs drift apart, which is what a real tape does as the wind changes.- A11y fix from the CSS original: the side badge used
--accent:#ef6c4don a 16%-alpha tint of itself over cream paper — about 2.7:1. The port darkens the accent tooklch(0.53 0.17 32)(≈4.8:1 on the tint). It is one character of text, and it is still text.
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 Cassette Mixtape Label Button
Source: https://codefronts.com/components/tailwind-css-buttons/tailwind-cassette-mixtape-label-button/
A Type-II cassette with a hand-written label: both reels turn while you hover, and clicking flips the shell to side B. The reels are conic gradients, the spool teeth a repeating conic, and the flip the same transform-3d construction as the coin.
## HTML
```html
<div class="tb-37 w-full min-h-svh grid place-items-center perspective-[1000px] font-tb37-sans p-[clamp(1.5rem,6vw,4rem)]">
<label class="group relative block cursor-pointer w-76 h-46 [-webkit-tap-highlight-color:transparent]">
<input type="checkbox" aria-label="Flip the cassette to side B" class="sr-only">
<span aria-hidden="true" class="relative block size-full transform-3d transition-[transform,translate] duration-[900ms] ease-tb37-out group-hover:-translate-y-[3px] group-active:scale-[0.985] group-has-checked:rotate-y-180 group-has-focus-visible:outline-2 group-has-focus-visible:outline-offset-[6px] group-has-focus-visible:outline-tb37-accent group-has-focus-visible:rounded-[0.55rem] motion-reduce:transition-none">
<span class="tb-37__face absolute inset-0 grid grid-rows-[auto_1fr] gap-3 p-3.5 rounded-[0.55rem] backface-hidden shadow-[inset_0_1px_0_#ffffff33,inset_0_-2px_6px_#00000080,0_18px_30px_-14px_#000000cc]">
<span class="flex items-center justify-between gap-4 px-6 py-2.5 rounded-[0.3rem] bg-[linear-gradient(180deg,#00000080,#00000047)] shadow-[inset_0_0_0_1px_#ffffff14]">
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-play-state:paused] group-hover:[animation-play-state:running] group-has-focus-visible:[animation-play-state:running] motion-reduce:animate-none"></span>
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-duration:4.6s] [animation-play-state:paused] group-hover:[animation-play-state:running] group-has-focus-visible:[animation-play-state:running] motion-reduce:animate-none"></span>
</span>
<span class="tb-37__label relative flex items-end justify-between gap-3 px-3 py-2 rounded-[0.2rem] shadow-[inset_0_0_0_1px_#0000001f]">
<span class="relative z-20 font-tb37-hand text-2xl font-bold text-tb37-ink">summer, mostly</span>
<span class="text-xs font-bold tracking-[0.14em] text-tb37-accent px-1.5 py-0.5 rounded-[0.2rem] bg-tb37-accent-tint">A</span>
</span>
</span>
<span class="tb-37__face absolute inset-0 grid grid-rows-[auto_1fr] gap-3 p-3.5 rounded-[0.55rem] backface-hidden rotate-y-180 shadow-[inset_0_1px_0_#ffffff33,inset_0_-2px_6px_#00000080,0_18px_30px_-14px_#000000cc]">
<span class="flex items-center justify-between gap-4 px-6 py-2.5 rounded-[0.3rem] bg-[linear-gradient(180deg,#00000080,#00000047)] shadow-[inset_0_0_0_1px_#ffffff14]">
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-play-state:paused] group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-duration:4.6s] [animation-play-state:paused] group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
</span>
<span class="tb-37__label relative flex items-end justify-between gap-3 px-3 py-2 rounded-[0.2rem] shadow-[inset_0_0_0_1px_#0000001f]">
<span class="relative z-20 font-tb37-hand text-2xl font-bold text-tb37-ink">the drive home</span>
<span class="text-xs font-bold tracking-[0.14em] text-tb37-accent px-1.5 py-0.5 rounded-[0.2rem] bg-tb37-accent-tint">B</span>
</span>
</span>
</span>
</label>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tb37-bg: #181a20;
--color-tb37-bg: oklch(0.2 0.012 275);
--color-tb37-shell: #2b2f38;
--color-tb37-shell: oklch(0.28 0.015 275);
--color-tb37-shell-hi: #3b414d;
--color-tb37-shell-hi: oklch(0.35 0.018 275);
--color-tb37-paper: #f2ead6;
--color-tb37-paper: oklch(0.93 0.03 90);
--color-tb37-ink: #2c2419;
--color-tb37-ink: oklch(0.26 0.025 75);
--color-tb37-hub: #dfe3ea;
--color-tb37-tape: #3a2a1c;
--color-tb37-teeth: #9aa2b0;
--color-tb37-accent: #c2452a;
--color-tb37-accent: oklch(0.53 0.17 32);
--color-tb37-accent-tint: #c2452a29;
--color-tb37-accent-tint: oklch(0.53 0.17 32 / 0.16);
--font-tb37-hand: "Caveat", ui-rounded, cursive;
--font-tb37-sans: "Archivo", ui-sans-serif, system-ui, sans-serif;
--ease-tb37-out: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb37-spin: tb-37-spin 3.4s linear infinite;
}
@keyframes tb-37-spin {
to {
rotate: 1turn;
}
}
.tb-37 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(34rem 20rem at 50% 44%, #23262f, transparent 74%), var(--color-tb37-bg);
}
/* PLASTIC SHELL: top-light over a diagonal body. */
.tb-37__face {
background: linear-gradient(180deg, rgba(255,255,255,.12), transparent 34%), linear-gradient(165deg, var(--color-tb37-shell-hi), var(--color-tb37-shell));
}
/* REELS: spool teeth (repeating conic) over hub + wound tape (radial). Four reels
across two faces must be identical, so the recipe lives once. */
.tb-37__reel {
background: repeating-conic-gradient(from 0deg, var(--color-tb37-teeth) 0 8deg, transparent 8deg 30deg), radial-gradient(circle, var(--color-tb37-hub) 30%, var(--color-tb37-tape) 33%, var(--color-tb37-tape) 80%, transparent 81%);
}
/* AGED LABEL + its ruled line. */
.tb-37__label {
background: linear-gradient(180deg, color-mix(in oklab, var(--color-tb37-paper) 92%, white), var(--color-tb37-paper));
}
.tb-37__label::after {
content: '';
position: absolute;
left: .8rem;
right: 3rem;
bottom: .55rem;
height: 1px;
background: color-mix(in oklab, var(--color-tb37-ink) 22%, transparent);
}
@media (prefers-color-scheme: light) {
.tb-37:not([data-theme="dark"]) {
--color-tb37-bg: oklch(0.24 0.015 275);
}
}
.tb-37[data-theme="light"] {
--color-tb37-bg: oklch(0.24 0.015 275);
}
@media (forced-colors: active) {
.tb-37__face {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-37__label {
background: Canvas;
}
.tb-37__label span {
color: CanvasText;
}
}
```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 Cassette Mixtape Label Button
Source: https://codefronts.com/components/tailwind-css-buttons/tailwind-cassette-mixtape-label-button/
A Type-II cassette with a hand-written label: both reels turn while you hover, and clicking flips the shell to side B. The reels are conic gradients, the spool teeth a repeating conic, and the flip the same transform-3d construction as the coin.
## HTML
```html
<div class="tb-37 w-full min-h-svh grid place-items-center perspective-[1000px] font-tb37-sans p-[clamp(1.5rem,6vw,4rem)]">
<label class="group relative block cursor-pointer w-76 h-46 [-webkit-tap-highlight-color:transparent]">
<input type="checkbox" aria-label="Flip the cassette to side B" class="sr-only">
<span aria-hidden="true" class="relative block size-full transform-3d transition-[transform,translate] duration-[900ms] ease-tb37-out group-hover:-translate-y-[3px] group-active:scale-[0.985] group-has-checked:rotate-y-180 group-has-focus-visible:outline-2 group-has-focus-visible:outline-offset-[6px] group-has-focus-visible:outline-tb37-accent group-has-focus-visible:rounded-[0.55rem] motion-reduce:transition-none">
<span class="tb-37__face absolute inset-0 grid grid-rows-[auto_1fr] gap-3 p-3.5 rounded-[0.55rem] backface-hidden shadow-[inset_0_1px_0_#ffffff33,inset_0_-2px_6px_#00000080,0_18px_30px_-14px_#000000cc]">
<span class="flex items-center justify-between gap-4 px-6 py-2.5 rounded-[0.3rem] bg-[linear-gradient(180deg,#00000080,#00000047)] shadow-[inset_0_0_0_1px_#ffffff14]">
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-play-state:paused] group-hover:[animation-play-state:running] group-has-focus-visible:[animation-play-state:running] motion-reduce:animate-none"></span>
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-duration:4.6s] [animation-play-state:paused] group-hover:[animation-play-state:running] group-has-focus-visible:[animation-play-state:running] motion-reduce:animate-none"></span>
</span>
<span class="tb-37__label relative flex items-end justify-between gap-3 px-3 py-2 rounded-[0.2rem] shadow-[inset_0_0_0_1px_#0000001f]">
<span class="relative z-20 font-tb37-hand text-2xl font-bold text-tb37-ink">summer, mostly</span>
<span class="text-xs font-bold tracking-[0.14em] text-tb37-accent px-1.5 py-0.5 rounded-[0.2rem] bg-tb37-accent-tint">A</span>
</span>
</span>
<span class="tb-37__face absolute inset-0 grid grid-rows-[auto_1fr] gap-3 p-3.5 rounded-[0.55rem] backface-hidden rotate-y-180 shadow-[inset_0_1px_0_#ffffff33,inset_0_-2px_6px_#00000080,0_18px_30px_-14px_#000000cc]">
<span class="flex items-center justify-between gap-4 px-6 py-2.5 rounded-[0.3rem] bg-[linear-gradient(180deg,#00000080,#00000047)] shadow-[inset_0_0_0_1px_#ffffff14]">
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-play-state:paused] group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
<span class="tb-37__reel block size-13 rounded-full shadow-[inset_0_0_0_1px_#ffffff1f] animate-tb37-spin [animation-duration:4.6s] [animation-play-state:paused] group-hover:[animation-play-state:running] motion-reduce:animate-none"></span>
</span>
<span class="tb-37__label relative flex items-end justify-between gap-3 px-3 py-2 rounded-[0.2rem] shadow-[inset_0_0_0_1px_#0000001f]">
<span class="relative z-20 font-tb37-hand text-2xl font-bold text-tb37-ink">the drive home</span>
<span class="text-xs font-bold tracking-[0.14em] text-tb37-accent px-1.5 py-0.5 rounded-[0.2rem] bg-tb37-accent-tint">B</span>
</span>
</span>
</span>
</label>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tb37-bg: #181a20;
--color-tb37-bg: oklch(0.2 0.012 275);
--color-tb37-shell: #2b2f38;
--color-tb37-shell: oklch(0.28 0.015 275);
--color-tb37-shell-hi: #3b414d;
--color-tb37-shell-hi: oklch(0.35 0.018 275);
--color-tb37-paper: #f2ead6;
--color-tb37-paper: oklch(0.93 0.03 90);
--color-tb37-ink: #2c2419;
--color-tb37-ink: oklch(0.26 0.025 75);
--color-tb37-hub: #dfe3ea;
--color-tb37-tape: #3a2a1c;
--color-tb37-teeth: #9aa2b0;
--color-tb37-accent: #c2452a;
--color-tb37-accent: oklch(0.53 0.17 32);
--color-tb37-accent-tint: #c2452a29;
--color-tb37-accent-tint: oklch(0.53 0.17 32 / 0.16);
--font-tb37-hand: "Caveat", ui-rounded, cursive;
--font-tb37-sans: "Archivo", ui-sans-serif, system-ui, sans-serif;
--ease-tb37-out: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb37-spin: tb-37-spin 3.4s linear infinite;
}
@keyframes tb-37-spin {
to {
rotate: 1turn;
}
}
.tb-37 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(34rem 20rem at 50% 44%, #23262f, transparent 74%), var(--color-tb37-bg);
}
/* PLASTIC SHELL: top-light over a diagonal body. */
.tb-37__face {
background: linear-gradient(180deg, rgba(255,255,255,.12), transparent 34%), linear-gradient(165deg, var(--color-tb37-shell-hi), var(--color-tb37-shell));
}
/* REELS: spool teeth (repeating conic) over hub + wound tape (radial). Four reels
across two faces must be identical, so the recipe lives once. */
.tb-37__reel {
background: repeating-conic-gradient(from 0deg, var(--color-tb37-teeth) 0 8deg, transparent 8deg 30deg), radial-gradient(circle, var(--color-tb37-hub) 30%, var(--color-tb37-tape) 33%, var(--color-tb37-tape) 80%, transparent 81%);
}
/* AGED LABEL + its ruled line. */
.tb-37__label {
background: linear-gradient(180deg, color-mix(in oklab, var(--color-tb37-paper) 92%, white), var(--color-tb37-paper));
}
.tb-37__label::after {
content: '';
position: absolute;
left: .8rem;
right: 3rem;
bottom: .55rem;
height: 1px;
background: color-mix(in oklab, var(--color-tb37-ink) 22%, transparent);
}
@media (prefers-color-scheme: light) {
.tb-37:not([data-theme="dark"]) {
--color-tb37-bg: oklch(0.24 0.015 275);
}
}
.tb-37[data-theme="light"] {
--color-tb37-bg: oklch(0.24 0.015 275);
}
@media (forced-colors: active) {
.tb-37__face {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-37__label {
background: Canvas;
}
.tb-37__label span {
color: CanvasText;
}
}
```How this works
Pausing beats conditionally applying. The naive port adds the animation on hover, which restarts the reels from 0° every time. Keeping the animation always applied and toggling animation-play-state means they resume:
class="animate-tb37-spin [animation-play-state:paused]
group-hover:[animation-play-state:running]"Infinite rotation at rest would be a distraction and an accessibility risk — hence paused by default, and motion-reduce:animate-none on top.
The reels are two conic gradients: a repeating-conic-gradient for the spool teeth over a radial for the hub and the wound tape. Both stay in CSS, shared by four reels across two faces — eight gradient layers that must be identical.
Make it yours
--color-tb37-shell: smoke grey, black and translucent blue were the real Type-I to Type-IV palette.- Reel speed is the two animation durations; 3–4s per turn matches a real capstan.
- Write your own label text — it is a normal span in a handwriting face.
- Drop the flip and keep the hover reels for a single-sided control.
- The
--color-tb37-accentside badge is the only saturated colour on the shell. Keep it that way.
Gotchas — read before shipping
- Infinite rotation at rest is a distraction and a motion risk. Paused until hover, always.
animation-play-state:pausedstill advances the timeline in some engines — verify the first frame.- 3D flips need
perspective-*on an ancestor andbackface-hiddenon both faces. - Skeuomorphic objects tempt tiny type in the label area. 1rem minimum or move it outside.
- Two conic gradients per reel × four reels is eight gradient layers. Acceptable once, not in a grid.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
repeating-conic-gradient and :has() are below Tailwind v4's own floor; the 3D transform utilities are v4-only.