39 / 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 Vinyl Record Sleeve Ripple Button
A record slides half out of its sleeve on hover and starts turning; press it and concentric ripples run out through the grooves like a needle drop. Grooves and label are pure gradients, and the sleeve carries real cover art with a flat fallback.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tb-39 w-full min-h-svh grid place-items-center font-tb39-sans antialiased p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" class="group relative isolate cursor-pointer border-0 bg-transparent p-0 size-46 [-webkit-tap-highlight-color:transparent] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tb39-label focus-visible:rounded-[0.35rem] disabled:cursor-not-allowed disabled:grayscale-[0.7]">
<span aria-hidden="true" class="tb-39__disc absolute z-10 inset-1.5 rounded-full shadow-[0_8px_18px_-6px_#000000cc,inset_0_0_0_1px_#ffffff0f] transition-transform duration-[550ms] ease-tb39-out group-hover:translate-x-[56%] group-hover:animate-tb39-spin group-focus-visible:translate-x-[56%] group-focus-visible:animate-tb39-spin motion-reduce:transition-none motion-reduce:group-hover:animate-none">
<span class="absolute inset-[20%] rounded-full border-2 border-white/60 opacity-0 group-active:animate-tb39-ripple motion-reduce:hidden"></span>
<span class="absolute inset-[20%] rounded-full border-2 border-white/60 opacity-0 [animation-delay:0.12s] group-active:animate-tb39-ripple motion-reduce:hidden"></span>
</span>
<span class="absolute z-20 inset-0 overflow-hidden grid content-end rounded-[0.25rem] bg-[linear-gradient(150deg,var(--color-tb39-sleeve),var(--color-tb39-sleeve-lo))] shadow-[0_20px_34px_-16px_#000000d9,inset_0_0_0_1px_#0000002e] transition-transform duration-200 group-active:scale-[0.99] motion-reduce:transition-none">
<img src="https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?w=700&q=70&auto=format&fit=crop" alt="" width="700" height="700" loading="lazy" decoding="async" class="absolute inset-0 size-full object-cover bg-tb39-sleeve-lo saturate-105 contrast-105">
<span aria-hidden="true" class="absolute inset-y-0 left-0 w-2 bg-[linear-gradient(90deg,#00000066,transparent)]"></span>
<span class="relative z-30 m-3 px-3 py-2 rounded-[0.25rem] text-[0.8125rem] font-extrabold tracking-[-0.01em] text-tb39-sleeve bg-tb39-scrim backdrop-blur-[3px]">Play side one</span>
</span>
</button>
</div><div class="tb-39 w-full min-h-svh grid place-items-center font-tb39-sans antialiased p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" class="group relative isolate cursor-pointer border-0 bg-transparent p-0 size-46 [-webkit-tap-highlight-color:transparent] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tb39-label focus-visible:rounded-[0.35rem] disabled:cursor-not-allowed disabled:grayscale-[0.7]">
<span aria-hidden="true" class="tb-39__disc absolute z-10 inset-1.5 rounded-full shadow-[0_8px_18px_-6px_#000000cc,inset_0_0_0_1px_#ffffff0f] transition-transform duration-[550ms] ease-tb39-out group-hover:translate-x-[56%] group-hover:animate-tb39-spin group-focus-visible:translate-x-[56%] group-focus-visible:animate-tb39-spin motion-reduce:transition-none motion-reduce:group-hover:animate-none">
<span class="absolute inset-[20%] rounded-full border-2 border-white/60 opacity-0 group-active:animate-tb39-ripple motion-reduce:hidden"></span>
<span class="absolute inset-[20%] rounded-full border-2 border-white/60 opacity-0 [animation-delay:0.12s] group-active:animate-tb39-ripple motion-reduce:hidden"></span>
</span>
<span class="absolute z-20 inset-0 overflow-hidden grid content-end rounded-[0.25rem] bg-[linear-gradient(150deg,var(--color-tb39-sleeve),var(--color-tb39-sleeve-lo))] shadow-[0_20px_34px_-16px_#000000d9,inset_0_0_0_1px_#0000002e] transition-transform duration-200 group-active:scale-[0.99] motion-reduce:transition-none">
<img src="https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?w=700&q=70&auto=format&fit=crop" alt="" width="700" height="700" loading="lazy" decoding="async" class="absolute inset-0 size-full object-cover bg-tb39-sleeve-lo saturate-105 contrast-105">
<span aria-hidden="true" class="absolute inset-y-0 left-0 w-2 bg-[linear-gradient(90deg,#00000066,transparent)]"></span>
<span class="relative z-30 m-3 px-3 py-2 rounded-[0.25rem] text-[0.8125rem] font-extrabold tracking-[-0.01em] text-tb39-sleeve bg-tb39-scrim backdrop-blur-[3px]">Play side one</span>
</span>
</button>
</div>@import "tailwindcss";
@theme {
--color-tb39-bg: #131316;
--color-tb39-bg: oklch(0.19 0.006 285);
--color-tb39-sleeve: #e9e4d8;
--color-tb39-sleeve: oklch(0.91 0.015 90);
--color-tb39-sleeve-lo: #c9c2b2;
--color-tb39-sleeve-lo: oklch(0.8 0.02 88);
--color-tb39-label: #d8483b;
--color-tb39-label: oklch(0.58 0.19 30);
--color-tb39-scrim: #0c0c0ec4;
--color-tb39-scrim: oklch(0.15 0.006 285 / 0.78);
--font-tb39-sans: "Archivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb39-out: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb39-spin: tb-39-spin 3.4s linear infinite;
--animate-tb39-ripple: tb-39-ripple 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes tb-39-spin {
to {
rotate: 1turn;
}
}
@keyframes tb-39-ripple {
0% {
opacity: 0.7;
scale: 0.3;
}
100% {
opacity: 0;
scale: 1.9;
}
}
.tb-39 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(34rem 22rem at 50% 44%, #1e1e24, transparent 74%), var(--color-tb39-bg);
}
/* THE RECORD: grooves (repeating radial), a spec sweep (conic), then the label
and vinyl (radial). Three layers that only read as vinyl together.
The spindle hole is a pseudo-element punched in the page colour. */
.tb-39__disc {
background: repeating-radial-gradient(circle at 50% 50%, rgba(255,255,255,.055) 0 1px, transparent 1px 3px), conic-gradient(from 210deg, rgba(255,255,255,.14), transparent 26%, rgba(255,255,255,.1) 56%, transparent 82%), radial-gradient(circle, var(--color-tb39-label) 0 25%, #1a1a1a 26%, #0b0b0b 100%);
}
.tb-39__disc::after {
content: '';
position: absolute;
inset: 47.5%;
border-radius: 50%;
background: var(--color-tb39-bg);
box-shadow: 0 0 0 1px rgba(0,0,0,.6);
}
@media (prefers-color-scheme: light) {
.tb-39:not([data-theme="dark"]) {
--color-tb39-bg: oklch(0.23 0.008 285);
}
}
.tb-39[data-theme="light"] {
--color-tb39-bg: oklch(0.23 0.008 285);
}
@media (forced-colors: active) {
.tb-39 button > span:last-child {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-39 button > span:last-child > span:last-child {
background: ButtonFace;
color: ButtonText;
}
.tb-39__disc {
display: none;
}
}@import "tailwindcss";
@theme {
--color-tb39-bg: #131316;
--color-tb39-bg: oklch(0.19 0.006 285);
--color-tb39-sleeve: #e9e4d8;
--color-tb39-sleeve: oklch(0.91 0.015 90);
--color-tb39-sleeve-lo: #c9c2b2;
--color-tb39-sleeve-lo: oklch(0.8 0.02 88);
--color-tb39-label: #d8483b;
--color-tb39-label: oklch(0.58 0.19 30);
--color-tb39-scrim: #0c0c0ec4;
--color-tb39-scrim: oklch(0.15 0.006 285 / 0.78);
--font-tb39-sans: "Archivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb39-out: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb39-spin: tb-39-spin 3.4s linear infinite;
--animate-tb39-ripple: tb-39-ripple 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes tb-39-spin {
to {
rotate: 1turn;
}
}
@keyframes tb-39-ripple {
0% {
opacity: 0.7;
scale: 0.3;
}
100% {
opacity: 0;
scale: 1.9;
}
}
.tb-39 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(34rem 22rem at 50% 44%, #1e1e24, transparent 74%), var(--color-tb39-bg);
}
/* THE RECORD: grooves (repeating radial), a spec sweep (conic), then the label
and vinyl (radial). Three layers that only read as vinyl together.
The spindle hole is a pseudo-element punched in the page colour. */
.tb-39__disc {
background: repeating-radial-gradient(circle at 50% 50%, rgba(255,255,255,.055) 0 1px, transparent 1px 3px), conic-gradient(from 210deg, rgba(255,255,255,.14), transparent 26%, rgba(255,255,255,.1) 56%, transparent 82%), radial-gradient(circle, var(--color-tb39-label) 0 25%, #1a1a1a 26%, #0b0b0b 100%);
}
.tb-39__disc::after {
content: '';
position: absolute;
inset: 47.5%;
border-radius: 50%;
background: var(--color-tb39-bg);
box-shadow: 0 0 0 1px rgba(0,0,0,.6);
}
@media (prefers-color-scheme: light) {
.tb-39:not([data-theme="dark"]) {
--color-tb39-bg: oklch(0.23 0.008 285);
}
}
.tb-39[data-theme="light"] {
--color-tb39-bg: oklch(0.23 0.008 285);
}
@media (forced-colors: active) {
.tb-39 button > span:last-child {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-39 button > span:last-child > span:last-child {
background: ButtonFace;
color: ButtonText;
}
.tb-39__disc {
display: 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-tb39-bg: #131316;
--color-tb39-bg: oklch(0.19 0.006 285);
--color-tb39-sleeve: #e9e4d8;
--color-tb39-sleeve: oklch(0.91 0.015 90);
--color-tb39-sleeve-lo: #c9c2b2;
--color-tb39-sleeve-lo: oklch(0.8 0.02 88);
--color-tb39-label: #d8483b;
--color-tb39-label: oklch(0.58 0.19 30);
--color-tb39-scrim: #0c0c0ec4;
--color-tb39-scrim: oklch(0.15 0.006 285 / 0.78);
--font-tb39-sans: "Archivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb39-out: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb39-spin: tb-39-spin 3.4s linear infinite;
--animate-tb39-ripple: tb-39-ripple 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes tb-39-spin {
to {
rotate: 1turn;
}
}
@keyframes tb-39-ripple {
0% {
opacity: 0.7;
scale: 0.3;
}
100% {
opacity: 0;
scale: 1.9;
}
}
.tb-39 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(34rem 22rem at 50% 44%, #1e1e24, transparent 74%), var(--color-tb39-bg);
}
/* THE RECORD: grooves (repeating radial), a spec sweep (conic), then the label
and vinyl (radial). Three layers that only read as vinyl together.
The spindle hole is a pseudo-element punched in the page colour. */
.tb-39__disc {
background: repeating-radial-gradient(circle at 50% 50%, rgba(255,255,255,.055) 0 1px, transparent 1px 3px), conic-gradient(from 210deg, rgba(255,255,255,.14), transparent 26%, rgba(255,255,255,.1) 56%, transparent 82%), radial-gradient(circle, var(--color-tb39-label) 0 25%, #1a1a1a 26%, #0b0b0b 100%);
}
.tb-39__disc::after {
content: '';
position: absolute;
inset: 47.5%;
border-radius: 50%;
background: var(--color-tb39-bg);
box-shadow: 0 0 0 1px rgba(0,0,0,.6);
}
@media (prefers-color-scheme: light) {
.tb-39:not([data-theme="dark"]) {
--color-tb39-bg: oklch(0.23 0.008 285);
}
}
.tb-39[data-theme="light"] {
--color-tb39-bg: oklch(0.23 0.008 285);
}
@media (forced-colors: active) {
.tb-39 button > span:last-child {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-39 button > span:last-child > span:last-child {
background: ButtonFace;
color: ButtonText;
}
.tb-39__disc {
display: none;
}
}@import "tailwindcss";
@theme {
--color-tb39-bg: #131316;
--color-tb39-bg: oklch(0.19 0.006 285);
--color-tb39-sleeve: #e9e4d8;
--color-tb39-sleeve: oklch(0.91 0.015 90);
--color-tb39-sleeve-lo: #c9c2b2;
--color-tb39-sleeve-lo: oklch(0.8 0.02 88);
--color-tb39-label: #d8483b;
--color-tb39-label: oklch(0.58 0.19 30);
--color-tb39-scrim: #0c0c0ec4;
--color-tb39-scrim: oklch(0.15 0.006 285 / 0.78);
--font-tb39-sans: "Archivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb39-out: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb39-spin: tb-39-spin 3.4s linear infinite;
--animate-tb39-ripple: tb-39-ripple 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes tb-39-spin {
to {
rotate: 1turn;
}
}
@keyframes tb-39-ripple {
0% {
opacity: 0.7;
scale: 0.3;
}
100% {
opacity: 0;
scale: 1.9;
}
}
.tb-39 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(34rem 22rem at 50% 44%, #1e1e24, transparent 74%), var(--color-tb39-bg);
}
/* THE RECORD: grooves (repeating radial), a spec sweep (conic), then the label
and vinyl (radial). Three layers that only read as vinyl together.
The spindle hole is a pseudo-element punched in the page colour. */
.tb-39__disc {
background: repeating-radial-gradient(circle at 50% 50%, rgba(255,255,255,.055) 0 1px, transparent 1px 3px), conic-gradient(from 210deg, rgba(255,255,255,.14), transparent 26%, rgba(255,255,255,.1) 56%, transparent 82%), radial-gradient(circle, var(--color-tb39-label) 0 25%, #1a1a1a 26%, #0b0b0b 100%);
}
.tb-39__disc::after {
content: '';
position: absolute;
inset: 47.5%;
border-radius: 50%;
background: var(--color-tb39-bg);
box-shadow: 0 0 0 1px rgba(0,0,0,.6);
}
@media (prefers-color-scheme: light) {
.tb-39:not([data-theme="dark"]) {
--color-tb39-bg: oklch(0.23 0.008 285);
}
}
.tb-39[data-theme="light"] {
--color-tb39-bg: oklch(0.23 0.008 285);
}
@media (forced-colors: active) {
.tb-39 button > span:last-child {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-39 button > span:last-child > span:last-child {
background: ButtonFace;
color: ButtonText;
}
.tb-39__disc {
display: 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=Archivo:wght@600;700;800&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=Archivo:wght@600;700;800&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Utilities: the slide, the spin, both ripple rings and their stagger, the sleeve, the scrim label, every state. CSS: the three-layer disc and its spindle hole.
group-hover:translate-x-[56%] group-hover:animate-tb39-spinon one element is the clearest demonstration in the collection of why individual transform properties matter: a transition and an animation touching the same property would fight, and these touch different ones.[animation-delay:0.12s]on the second ripple is a one-word replacement for.ring:last-child{ animation-delay:.12s }. Two identical rings differing by a delay is exactly the case where markup beats a selector.- A11y fix from the CSS original: the sleeve's caption sat on
rgba(12,12,14,.72)over arbitrary cover art. Against a bright photo region that scrim let through enough light to land near 4.2:1. The port raises it to 0.78 alpha as a named token (--color-tb39-scrim), which holds ≈5.5:1 over the brightest part of the default image and is now a single value to tune for a different cover.
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 Vinyl Record Sleeve Ripple Button
Source: https://codefronts.com/components/tailwind-css-buttons/tailwind-vinyl-record-sleeve-ripple-button/
A record slides half out of its sleeve on hover and starts turning; press it and concentric ripples run out through the grooves like a needle drop. Grooves and label are pure gradients, and the sleeve carries real cover art with a flat fallback.
## HTML
```html
<div class="tb-39 w-full min-h-svh grid place-items-center font-tb39-sans antialiased p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" class="group relative isolate cursor-pointer border-0 bg-transparent p-0 size-46 [-webkit-tap-highlight-color:transparent] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tb39-label focus-visible:rounded-[0.35rem] disabled:cursor-not-allowed disabled:grayscale-[0.7]">
<span aria-hidden="true" class="tb-39__disc absolute z-10 inset-1.5 rounded-full shadow-[0_8px_18px_-6px_#000000cc,inset_0_0_0_1px_#ffffff0f] transition-transform duration-[550ms] ease-tb39-out group-hover:translate-x-[56%] group-hover:animate-tb39-spin group-focus-visible:translate-x-[56%] group-focus-visible:animate-tb39-spin motion-reduce:transition-none motion-reduce:group-hover:animate-none">
<span class="absolute inset-[20%] rounded-full border-2 border-white/60 opacity-0 group-active:animate-tb39-ripple motion-reduce:hidden"></span>
<span class="absolute inset-[20%] rounded-full border-2 border-white/60 opacity-0 [animation-delay:0.12s] group-active:animate-tb39-ripple motion-reduce:hidden"></span>
</span>
<span class="absolute z-20 inset-0 overflow-hidden grid content-end rounded-[0.25rem] bg-[linear-gradient(150deg,var(--color-tb39-sleeve),var(--color-tb39-sleeve-lo))] shadow-[0_20px_34px_-16px_#000000d9,inset_0_0_0_1px_#0000002e] transition-transform duration-200 group-active:scale-[0.99] motion-reduce:transition-none">
<img src="https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?w=700&q=70&auto=format&fit=crop" alt="" width="700" height="700" loading="lazy" decoding="async" class="absolute inset-0 size-full object-cover bg-tb39-sleeve-lo saturate-105 contrast-105">
<span aria-hidden="true" class="absolute inset-y-0 left-0 w-2 bg-[linear-gradient(90deg,#00000066,transparent)]"></span>
<span class="relative z-30 m-3 px-3 py-2 rounded-[0.25rem] text-[0.8125rem] font-extrabold tracking-[-0.01em] text-tb39-sleeve bg-tb39-scrim backdrop-blur-[3px]">Play side one</span>
</span>
</button>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tb39-bg: #131316;
--color-tb39-bg: oklch(0.19 0.006 285);
--color-tb39-sleeve: #e9e4d8;
--color-tb39-sleeve: oklch(0.91 0.015 90);
--color-tb39-sleeve-lo: #c9c2b2;
--color-tb39-sleeve-lo: oklch(0.8 0.02 88);
--color-tb39-label: #d8483b;
--color-tb39-label: oklch(0.58 0.19 30);
--color-tb39-scrim: #0c0c0ec4;
--color-tb39-scrim: oklch(0.15 0.006 285 / 0.78);
--font-tb39-sans: "Archivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb39-out: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb39-spin: tb-39-spin 3.4s linear infinite;
--animate-tb39-ripple: tb-39-ripple 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes tb-39-spin {
to {
rotate: 1turn;
}
}
@keyframes tb-39-ripple {
0% {
opacity: 0.7;
scale: 0.3;
}
100% {
opacity: 0;
scale: 1.9;
}
}
.tb-39 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(34rem 22rem at 50% 44%, #1e1e24, transparent 74%), var(--color-tb39-bg);
}
/* THE RECORD: grooves (repeating radial), a spec sweep (conic), then the label
and vinyl (radial). Three layers that only read as vinyl together.
The spindle hole is a pseudo-element punched in the page colour. */
.tb-39__disc {
background: repeating-radial-gradient(circle at 50% 50%, rgba(255,255,255,.055) 0 1px, transparent 1px 3px), conic-gradient(from 210deg, rgba(255,255,255,.14), transparent 26%, rgba(255,255,255,.1) 56%, transparent 82%), radial-gradient(circle, var(--color-tb39-label) 0 25%, #1a1a1a 26%, #0b0b0b 100%);
}
.tb-39__disc::after {
content: '';
position: absolute;
inset: 47.5%;
border-radius: 50%;
background: var(--color-tb39-bg);
box-shadow: 0 0 0 1px rgba(0,0,0,.6);
}
@media (prefers-color-scheme: light) {
.tb-39:not([data-theme="dark"]) {
--color-tb39-bg: oklch(0.23 0.008 285);
}
}
.tb-39[data-theme="light"] {
--color-tb39-bg: oklch(0.23 0.008 285);
}
@media (forced-colors: active) {
.tb-39 button > span:last-child {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-39 button > span:last-child > span:last-child {
background: ButtonFace;
color: ButtonText;
}
.tb-39__disc {
display: 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 Vinyl Record Sleeve Ripple Button
Source: https://codefronts.com/components/tailwind-css-buttons/tailwind-vinyl-record-sleeve-ripple-button/
A record slides half out of its sleeve on hover and starts turning; press it and concentric ripples run out through the grooves like a needle drop. Grooves and label are pure gradients, and the sleeve carries real cover art with a flat fallback.
## HTML
```html
<div class="tb-39 w-full min-h-svh grid place-items-center font-tb39-sans antialiased p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" class="group relative isolate cursor-pointer border-0 bg-transparent p-0 size-46 [-webkit-tap-highlight-color:transparent] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tb39-label focus-visible:rounded-[0.35rem] disabled:cursor-not-allowed disabled:grayscale-[0.7]">
<span aria-hidden="true" class="tb-39__disc absolute z-10 inset-1.5 rounded-full shadow-[0_8px_18px_-6px_#000000cc,inset_0_0_0_1px_#ffffff0f] transition-transform duration-[550ms] ease-tb39-out group-hover:translate-x-[56%] group-hover:animate-tb39-spin group-focus-visible:translate-x-[56%] group-focus-visible:animate-tb39-spin motion-reduce:transition-none motion-reduce:group-hover:animate-none">
<span class="absolute inset-[20%] rounded-full border-2 border-white/60 opacity-0 group-active:animate-tb39-ripple motion-reduce:hidden"></span>
<span class="absolute inset-[20%] rounded-full border-2 border-white/60 opacity-0 [animation-delay:0.12s] group-active:animate-tb39-ripple motion-reduce:hidden"></span>
</span>
<span class="absolute z-20 inset-0 overflow-hidden grid content-end rounded-[0.25rem] bg-[linear-gradient(150deg,var(--color-tb39-sleeve),var(--color-tb39-sleeve-lo))] shadow-[0_20px_34px_-16px_#000000d9,inset_0_0_0_1px_#0000002e] transition-transform duration-200 group-active:scale-[0.99] motion-reduce:transition-none">
<img src="https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?w=700&q=70&auto=format&fit=crop" alt="" width="700" height="700" loading="lazy" decoding="async" class="absolute inset-0 size-full object-cover bg-tb39-sleeve-lo saturate-105 contrast-105">
<span aria-hidden="true" class="absolute inset-y-0 left-0 w-2 bg-[linear-gradient(90deg,#00000066,transparent)]"></span>
<span class="relative z-30 m-3 px-3 py-2 rounded-[0.25rem] text-[0.8125rem] font-extrabold tracking-[-0.01em] text-tb39-sleeve bg-tb39-scrim backdrop-blur-[3px]">Play side one</span>
</span>
</button>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tb39-bg: #131316;
--color-tb39-bg: oklch(0.19 0.006 285);
--color-tb39-sleeve: #e9e4d8;
--color-tb39-sleeve: oklch(0.91 0.015 90);
--color-tb39-sleeve-lo: #c9c2b2;
--color-tb39-sleeve-lo: oklch(0.8 0.02 88);
--color-tb39-label: #d8483b;
--color-tb39-label: oklch(0.58 0.19 30);
--color-tb39-scrim: #0c0c0ec4;
--color-tb39-scrim: oklch(0.15 0.006 285 / 0.78);
--font-tb39-sans: "Archivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb39-out: cubic-bezier(0.16, 1, 0.3, 1);
--animate-tb39-spin: tb-39-spin 3.4s linear infinite;
--animate-tb39-ripple: tb-39-ripple 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes tb-39-spin {
to {
rotate: 1turn;
}
}
@keyframes tb-39-ripple {
0% {
opacity: 0.7;
scale: 0.3;
}
100% {
opacity: 0;
scale: 1.9;
}
}
.tb-39 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(34rem 22rem at 50% 44%, #1e1e24, transparent 74%), var(--color-tb39-bg);
}
/* THE RECORD: grooves (repeating radial), a spec sweep (conic), then the label
and vinyl (radial). Three layers that only read as vinyl together.
The spindle hole is a pseudo-element punched in the page colour. */
.tb-39__disc {
background: repeating-radial-gradient(circle at 50% 50%, rgba(255,255,255,.055) 0 1px, transparent 1px 3px), conic-gradient(from 210deg, rgba(255,255,255,.14), transparent 26%, rgba(255,255,255,.1) 56%, transparent 82%), radial-gradient(circle, var(--color-tb39-label) 0 25%, #1a1a1a 26%, #0b0b0b 100%);
}
.tb-39__disc::after {
content: '';
position: absolute;
inset: 47.5%;
border-radius: 50%;
background: var(--color-tb39-bg);
box-shadow: 0 0 0 1px rgba(0,0,0,.6);
}
@media (prefers-color-scheme: light) {
.tb-39:not([data-theme="dark"]) {
--color-tb39-bg: oklch(0.23 0.008 285);
}
}
.tb-39[data-theme="light"] {
--color-tb39-bg: oklch(0.23 0.008 285);
}
@media (forced-colors: active) {
.tb-39 button > span:last-child {
background: ButtonFace;
border: 1px solid ButtonText;
box-shadow: none;
}
.tb-39 button > span:last-child > span:last-child {
background: ButtonFace;
color: ButtonText;
}
.tb-39__disc {
display: none;
}
}
```How this works
The slide and the spin are independent properties, which is the only reason this works as two utilities rather than one keyframe doing both:
group-hover:translate-x-[56%] group-hover:animate-tb39-spinIf both lived in a transform string the transition and the animation would fight — a real bug in the pre-individual-property era, and worth remembering when someone suggests "simplifying" this to one keyframe.
The reveal fires on group-focus-visible: too, so keyboard users see the record come out; under motion-reduce: the disc still slides to its revealed position but does not spin.
The grooves stay CSS — three stacked gradients (grooves, a conic spec sweep, the label-and-vinyl radial) that only read as vinyl together.
Make it yours
- Groove density is the
1px/3pxstops: tighter reads as a 33rpm LP, looser as a 45. --color-tb39-labelrecolours the centre; drop your own text on it.- Reveal distance is
translate-x-[56%]— a peek versus a full pull-out. - Swap the cover image; the fallback colour behind it keeps the layout safe.
- Ripple count: add a third ring with a longer delay for a heavier needle drop.
Gotchas — read before shipping
- Spin on hover only. Infinite rotation at rest is a distraction, and the
focus-visibletwin is what keeps it keyboard-equal. repeating-radial-gradientat 1px stops aliases badly on non-integer DPR. Whole-pixel disc sizes where you can.isolateon the wrapper avoids stacking surprises between disc, sleeve and page.- Don't animate rotate and translate in the same keyframe if you also transition them.
- Cover art takes
alt=""— the button's own label carries the meaning, and a description would be announced twice.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
repeating-radial-gradient, conic-gradient and individual transform utilities are all baseline below Tailwind v4's floor.