26 / 36
All 36 in 36 Tailwind Stacked Cards
- 01Tailwind Scroll-Driven Scaling Card Stack
- 02Tailwind Swipe Card Deck Drag to Dismiss
- 03Tailwind Glassmorphism Testimonial Card Stack
- 04Tailwind Stacked Cards Expand Accordion on Click
- 05Tailwind Fan Out Photo Stack on Hover
- 06Tailwind 3D Perspective Tilt Card Stack
- 07Tailwind Multi Step Form Card Stack
- 08Tailwind Tabbed Card Stack Bring to Front
- 09Tailwind Click to Front Stacked Cards Cycle
- 10Tailwind Classic Card Deck Stack Shadow
- 11Tailwind Fan Spread Hand of Cards
- 12Tailwind Polaroid Photo Stack Scattered Rotate
- 13Tailwind Staircase Offset Stacked Cards Cascade
- 14Tailwind Cards Spread Out Horizontally on Hover
- 15Tailwind Stacked Cards Lift on Hover Sibling Push
- 16Tailwind Accordion Stacked Cards Slide Out Menu
- 17Tailwind Shuffle Cards Animation Loop Deck
- 18Tailwind File Folder Tab Stacked Cards
- 19Tailwind Scattered Photos Pinch to Centre on Hover
- 20Tailwind Mobile Swipe Card Stack Translate3d
- 21Tailwind Single Accordion Card Nested Layers
- 22Tailwind Isometric Tower Stacked Cards 3D Column
- 23Tailwind 3D Flip Card Stack Reveal Back
- 24Tailwind Card Stack Tilt on Hover Quadrant
- 25Tailwind Notification Stack Pile Dismiss
- 26Tailwind Receipt Roll Unroll Stacked Items
- 27Tailwind Scratch Card Reveal Canvas Mask
- 28Tailwind Stacked Cards Hover Reveal Spread
- 29Tailwind Scroll Activated Sticky Stacking Cards
- 30Tailwind Stacked Testimonial Slider Radio
- 31Tailwind Isometric Overlapping Stacked Cards
- 32Tailwind Bento Grid Stacked Cards Hover Fan
- 33Tailwind Perspective Card Deck Depth Tunnel
- 34Tailwind Peel Back Corner Card Reveal
- 35Tailwind Timeline Stacked Milestone Cards
- 36Tailwind Spiral Tower Rotating Stacked Cards
Tailwind Receipt Roll Unroll Stacked Items
An order summary that unrolls like till paper. The reveal is the familiar grid-rows-[0fr] → 1fr pair, but the memorable part is the perforated edge: one arbitrary [mask:radial-gradient(9px_at_50%_0,transparent_98%,#000)_repeat-x_0_0/18px_12px] cuts the zig-zag out of a plain block, and the direction arrow is a peer-checked:after:content swap rather than two rules.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="stk-26 w-full min-h-svh flex items-start justify-center px-5 py-[10vh] font-stk26-sans bg-stk26-desk" aria-label="Unrolling receipt">
<div class="relative w-[min(300px,100%)] rounded-t-[4px] bg-stk26-paper pb-3.5 shadow-[0_24px_50px_-24px_rgba(0,0,0,0.6)]">
<input class="peer sr-only" type="checkbox" id="stk-26-t" checked>
<label class="flex cursor-pointer flex-col items-center gap-1 border-b-2 border-dashed border-stk26-dash px-4 pt-5 pb-3.5 peer-focus-visible:outline-3 peer-focus-visible:outline-stk26-ring peer-focus-visible:outline-offset-2" for="stk-26-t">
<b class="font-stk26-mono text-[13px] font-bold tracking-[0.06em] text-stk26-ink">CODEFRONTS · Order #2261</b>
<span class="font-stk26-mono text-[11px] tracking-[0.12em] uppercase text-stk26-faint after:content-['_▼'] peer-checked:after:content-['_▲']">Tap to roll</span>
</label>
<div class="grid grid-rows-[0fr] transition-[grid-template-rows] duration-550 ease-stk26 peer-checked:grid-rows-[1fr] motion-reduce:transition-none">
<div class="overflow-hidden font-stk26-mono">
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Stacked Cards Pack</span><span>$29.00</span></div>
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Carousel Add-on</span><span>$14.00</span></div>
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Lifetime updates</span><span>$12.00</span></div>
<div class="mt-1.5 flex justify-between border-t border-dashed border-stk26-dash px-5 py-2.25 text-[13px] text-stk26-body"><span>Subtotal</span><span>$55.00</span></div>
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Tax</span><span>$4.40</span></div>
<div class="flex justify-between border-t-2 border-stk26-ink px-5 py-2.25 text-[15px] font-bold text-stk26-ink"><span>Total</span><span>$59.40</span></div>
<p class="m-0 px-5 pt-3.5 pb-1.5 text-center font-stk26-mono text-xs tracking-[0.1em] text-stk26-faint">★ Thank you for your order ★</p>
</div>
</div>
<span class="stk-26__perf block h-3 bg-stk26-paper [mask:radial-gradient(9px_at_50%_0,transparent_98%,#000)_repeat-x_0_0/18px_12px]" aria-hidden="true"></span>
</div>
</section><section class="stk-26 w-full min-h-svh flex items-start justify-center px-5 py-[10vh] font-stk26-sans bg-stk26-desk" aria-label="Unrolling receipt">
<div class="relative w-[min(300px,100%)] rounded-t-[4px] bg-stk26-paper pb-3.5 shadow-[0_24px_50px_-24px_rgba(0,0,0,0.6)]">
<input class="peer sr-only" type="checkbox" id="stk-26-t" checked>
<label class="flex cursor-pointer flex-col items-center gap-1 border-b-2 border-dashed border-stk26-dash px-4 pt-5 pb-3.5 peer-focus-visible:outline-3 peer-focus-visible:outline-stk26-ring peer-focus-visible:outline-offset-2" for="stk-26-t">
<b class="font-stk26-mono text-[13px] font-bold tracking-[0.06em] text-stk26-ink">CODEFRONTS · Order #2261</b>
<span class="font-stk26-mono text-[11px] tracking-[0.12em] uppercase text-stk26-faint after:content-['_▼'] peer-checked:after:content-['_▲']">Tap to roll</span>
</label>
<div class="grid grid-rows-[0fr] transition-[grid-template-rows] duration-550 ease-stk26 peer-checked:grid-rows-[1fr] motion-reduce:transition-none">
<div class="overflow-hidden font-stk26-mono">
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Stacked Cards Pack</span><span>$29.00</span></div>
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Carousel Add-on</span><span>$14.00</span></div>
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Lifetime updates</span><span>$12.00</span></div>
<div class="mt-1.5 flex justify-between border-t border-dashed border-stk26-dash px-5 py-2.25 text-[13px] text-stk26-body"><span>Subtotal</span><span>$55.00</span></div>
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Tax</span><span>$4.40</span></div>
<div class="flex justify-between border-t-2 border-stk26-ink px-5 py-2.25 text-[15px] font-bold text-stk26-ink"><span>Total</span><span>$59.40</span></div>
<p class="m-0 px-5 pt-3.5 pb-1.5 text-center font-stk26-mono text-xs tracking-[0.1em] text-stk26-faint">★ Thank you for your order ★</p>
</div>
</div>
<span class="stk-26__perf block h-3 bg-stk26-paper [mask:radial-gradient(9px_at_50%_0,transparent_98%,#000)_repeat-x_0_0/18px_12px]" aria-hidden="true"></span>
</div>
</section>@import "tailwindcss";
@theme {
--color-stk26-desk: #2b2a28;
--color-stk26-desk: oklch(0.28 0.006 70);
--color-stk26-paper: #fbfaf5;
--color-stk26-paper: oklch(0.98 0.006 85);
--color-stk26-dash: #d8d4c8;
--color-stk26-dash: oklch(0.86 0.012 85);
--color-stk26-ink: #2a2620;
--color-stk26-ink: oklch(0.26 0.010 75);
--color-stk26-body: #3a352c;
--color-stk26-body: oklch(0.34 0.012 80);
--color-stk26-faint: #8a8474;
--color-stk26-faint: oklch(0.60 0.016 85);
--color-stk26-ring: #5b78e8;
--color-stk26-ring: oklch(0.60 0.17 262);
--font-stk26-sans: 'Archivo', system-ui, sans-serif;
--font-stk26-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk26: cubic-bezier(0.4, 0.9, 0.3, 1);
}
/* Older Safari and Chrome need the prefixed mask, and one class list cannot
carry two declarations of the same property. */
.stk-26__perf {
-webkit-mask: radial-gradient(9px at 50% 0, transparent 98%, #000) repeat-x 0 0 / 18px 12px;
}@import "tailwindcss";
@theme {
--color-stk26-desk: #2b2a28;
--color-stk26-desk: oklch(0.28 0.006 70);
--color-stk26-paper: #fbfaf5;
--color-stk26-paper: oklch(0.98 0.006 85);
--color-stk26-dash: #d8d4c8;
--color-stk26-dash: oklch(0.86 0.012 85);
--color-stk26-ink: #2a2620;
--color-stk26-ink: oklch(0.26 0.010 75);
--color-stk26-body: #3a352c;
--color-stk26-body: oklch(0.34 0.012 80);
--color-stk26-faint: #8a8474;
--color-stk26-faint: oklch(0.60 0.016 85);
--color-stk26-ring: #5b78e8;
--color-stk26-ring: oklch(0.60 0.17 262);
--font-stk26-sans: 'Archivo', system-ui, sans-serif;
--font-stk26-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk26: cubic-bezier(0.4, 0.9, 0.3, 1);
}
/* Older Safari and Chrome need the prefixed mask, and one class list cannot
carry two declarations of the same property. */
.stk-26__perf {
-webkit-mask: radial-gradient(9px at 50% 0, transparent 98%, #000) repeat-x 0 0 / 18px 12px;
}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-stk26-desk: #2b2a28;
--color-stk26-desk: oklch(0.28 0.006 70);
--color-stk26-paper: #fbfaf5;
--color-stk26-paper: oklch(0.98 0.006 85);
--color-stk26-dash: #d8d4c8;
--color-stk26-dash: oklch(0.86 0.012 85);
--color-stk26-ink: #2a2620;
--color-stk26-ink: oklch(0.26 0.010 75);
--color-stk26-body: #3a352c;
--color-stk26-body: oklch(0.34 0.012 80);
--color-stk26-faint: #8a8474;
--color-stk26-faint: oklch(0.60 0.016 85);
--color-stk26-ring: #5b78e8;
--color-stk26-ring: oklch(0.60 0.17 262);
--font-stk26-sans: 'Archivo', system-ui, sans-serif;
--font-stk26-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk26: cubic-bezier(0.4, 0.9, 0.3, 1);
}
/* Older Safari and Chrome need the prefixed mask, and one class list cannot
carry two declarations of the same property. */
.stk-26__perf {
-webkit-mask: radial-gradient(9px at 50% 0, transparent 98%, #000) repeat-x 0 0 / 18px 12px;
}@import "tailwindcss";
@theme {
--color-stk26-desk: #2b2a28;
--color-stk26-desk: oklch(0.28 0.006 70);
--color-stk26-paper: #fbfaf5;
--color-stk26-paper: oklch(0.98 0.006 85);
--color-stk26-dash: #d8d4c8;
--color-stk26-dash: oklch(0.86 0.012 85);
--color-stk26-ink: #2a2620;
--color-stk26-ink: oklch(0.26 0.010 75);
--color-stk26-body: #3a352c;
--color-stk26-body: oklch(0.34 0.012 80);
--color-stk26-faint: #8a8474;
--color-stk26-faint: oklch(0.60 0.016 85);
--color-stk26-ring: #5b78e8;
--color-stk26-ring: oklch(0.60 0.17 262);
--font-stk26-sans: 'Archivo', system-ui, sans-serif;
--font-stk26-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk26: cubic-bezier(0.4, 0.9, 0.3, 1);
}
/* Older Safari and Chrome need the prefixed mask, and one class list cannot
carry two declarations of the same property. */
.stk-26__perf {
-webkit-mask: radial-gradient(9px at 50% 0, transparent 98%, #000) repeat-x 0 0 / 18px 12px;
}<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:wdth,wght@75..125,400;75..125,800&family=JetBrains+Mono:wght@400;500;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=Archivo:wdth,wght@75..125,400;75..125,800&family=JetBrains+Mono:wght@400;500;700&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. - The perforated paper edge is one arbitrary
maskwith every space underscore-escaped — no SVG, no borders, no extra elements. - The
-webkit-maskfallback is the only selector in the stylesheet, for the same reason as demo 06's mask-composite: two values of one property cannot share a class list. - The arrow direction swaps via
after:content-['_▼']andpeer-checked:after:content-['_▲']— the case wherecontentin a utility is genuinely right, because the difference is one glyph. - The
0fr → 1frgrid reveal appears for the third time in the collection (04, 16, 26); it is the pattern to reach for whenever something must expand toheight:auto. - The monospace face is a
--font-*token, so the totals column is aligned by the same mechanism as any colour. - Every figure is real selectable text in flex rows, so the summary reads in order to a screen reader and the numbers can be copied.
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 Receipt Roll Unroll Stacked Items
Source: https://codefronts.com/components/tailwind-stacked-cards/tailwind-receipt-roll-unroll-stacked-items/
An order summary that unrolls like till paper. The reveal is the familiar grid-rows-[0fr] → 1fr pair, but the memorable part is the perforated edge: one arbitrary [mask:radial-gradient(9px_at_50%_0,transparent_98%,#000)_repeat-x_0_0/18px_12px] cuts the zig-zag out of a plain block, and the direction arrow is a peer-checked:after:content swap rather than two rules.
## HTML
```html
<section class="stk-26 w-full min-h-svh flex items-start justify-center px-5 py-[10vh] font-stk26-sans bg-stk26-desk" aria-label="Unrolling receipt">
<div class="relative w-[min(300px,100%)] rounded-t-[4px] bg-stk26-paper pb-3.5 shadow-[0_24px_50px_-24px_rgba(0,0,0,0.6)]">
<input class="peer sr-only" type="checkbox" id="stk-26-t" checked>
<label class="flex cursor-pointer flex-col items-center gap-1 border-b-2 border-dashed border-stk26-dash px-4 pt-5 pb-3.5 peer-focus-visible:outline-3 peer-focus-visible:outline-stk26-ring peer-focus-visible:outline-offset-2" for="stk-26-t">
<b class="font-stk26-mono text-[13px] font-bold tracking-[0.06em] text-stk26-ink">CODEFRONTS · Order #2261</b>
<span class="font-stk26-mono text-[11px] tracking-[0.12em] uppercase text-stk26-faint after:content-['_▼'] peer-checked:after:content-['_▲']">Tap to roll</span>
</label>
<div class="grid grid-rows-[0fr] transition-[grid-template-rows] duration-550 ease-stk26 peer-checked:grid-rows-[1fr] motion-reduce:transition-none">
<div class="overflow-hidden font-stk26-mono">
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Stacked Cards Pack</span><span>$29.00</span></div>
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Carousel Add-on</span><span>$14.00</span></div>
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Lifetime updates</span><span>$12.00</span></div>
<div class="mt-1.5 flex justify-between border-t border-dashed border-stk26-dash px-5 py-2.25 text-[13px] text-stk26-body"><span>Subtotal</span><span>$55.00</span></div>
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Tax</span><span>$4.40</span></div>
<div class="flex justify-between border-t-2 border-stk26-ink px-5 py-2.25 text-[15px] font-bold text-stk26-ink"><span>Total</span><span>$59.40</span></div>
<p class="m-0 px-5 pt-3.5 pb-1.5 text-center font-stk26-mono text-xs tracking-[0.1em] text-stk26-faint">★ Thank you for your order ★</p>
</div>
</div>
<span class="stk-26__perf block h-3 bg-stk26-paper [mask:radial-gradient(9px_at_50%_0,transparent_98%,#000)_repeat-x_0_0/18px_12px]" aria-hidden="true"></span>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-stk26-desk: #2b2a28;
--color-stk26-desk: oklch(0.28 0.006 70);
--color-stk26-paper: #fbfaf5;
--color-stk26-paper: oklch(0.98 0.006 85);
--color-stk26-dash: #d8d4c8;
--color-stk26-dash: oklch(0.86 0.012 85);
--color-stk26-ink: #2a2620;
--color-stk26-ink: oklch(0.26 0.010 75);
--color-stk26-body: #3a352c;
--color-stk26-body: oklch(0.34 0.012 80);
--color-stk26-faint: #8a8474;
--color-stk26-faint: oklch(0.60 0.016 85);
--color-stk26-ring: #5b78e8;
--color-stk26-ring: oklch(0.60 0.17 262);
--font-stk26-sans: 'Archivo', system-ui, sans-serif;
--font-stk26-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk26: cubic-bezier(0.4, 0.9, 0.3, 1);
}
/* Older Safari and Chrome need the prefixed mask, and one class list cannot
carry two declarations of the same property. */
.stk-26__perf {
-webkit-mask: radial-gradient(9px at 50% 0, transparent 98%, #000) repeat-x 0 0 / 18px 12px;
}
```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 Receipt Roll Unroll Stacked Items
Source: https://codefronts.com/components/tailwind-stacked-cards/tailwind-receipt-roll-unroll-stacked-items/
An order summary that unrolls like till paper. The reveal is the familiar grid-rows-[0fr] → 1fr pair, but the memorable part is the perforated edge: one arbitrary [mask:radial-gradient(9px_at_50%_0,transparent_98%,#000)_repeat-x_0_0/18px_12px] cuts the zig-zag out of a plain block, and the direction arrow is a peer-checked:after:content swap rather than two rules.
## HTML
```html
<section class="stk-26 w-full min-h-svh flex items-start justify-center px-5 py-[10vh] font-stk26-sans bg-stk26-desk" aria-label="Unrolling receipt">
<div class="relative w-[min(300px,100%)] rounded-t-[4px] bg-stk26-paper pb-3.5 shadow-[0_24px_50px_-24px_rgba(0,0,0,0.6)]">
<input class="peer sr-only" type="checkbox" id="stk-26-t" checked>
<label class="flex cursor-pointer flex-col items-center gap-1 border-b-2 border-dashed border-stk26-dash px-4 pt-5 pb-3.5 peer-focus-visible:outline-3 peer-focus-visible:outline-stk26-ring peer-focus-visible:outline-offset-2" for="stk-26-t">
<b class="font-stk26-mono text-[13px] font-bold tracking-[0.06em] text-stk26-ink">CODEFRONTS · Order #2261</b>
<span class="font-stk26-mono text-[11px] tracking-[0.12em] uppercase text-stk26-faint after:content-['_▼'] peer-checked:after:content-['_▲']">Tap to roll</span>
</label>
<div class="grid grid-rows-[0fr] transition-[grid-template-rows] duration-550 ease-stk26 peer-checked:grid-rows-[1fr] motion-reduce:transition-none">
<div class="overflow-hidden font-stk26-mono">
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Stacked Cards Pack</span><span>$29.00</span></div>
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Carousel Add-on</span><span>$14.00</span></div>
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Lifetime updates</span><span>$12.00</span></div>
<div class="mt-1.5 flex justify-between border-t border-dashed border-stk26-dash px-5 py-2.25 text-[13px] text-stk26-body"><span>Subtotal</span><span>$55.00</span></div>
<div class="flex justify-between px-5 py-2.25 text-[13px] text-stk26-body"><span>Tax</span><span>$4.40</span></div>
<div class="flex justify-between border-t-2 border-stk26-ink px-5 py-2.25 text-[15px] font-bold text-stk26-ink"><span>Total</span><span>$59.40</span></div>
<p class="m-0 px-5 pt-3.5 pb-1.5 text-center font-stk26-mono text-xs tracking-[0.1em] text-stk26-faint">★ Thank you for your order ★</p>
</div>
</div>
<span class="stk-26__perf block h-3 bg-stk26-paper [mask:radial-gradient(9px_at_50%_0,transparent_98%,#000)_repeat-x_0_0/18px_12px]" aria-hidden="true"></span>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-stk26-desk: #2b2a28;
--color-stk26-desk: oklch(0.28 0.006 70);
--color-stk26-paper: #fbfaf5;
--color-stk26-paper: oklch(0.98 0.006 85);
--color-stk26-dash: #d8d4c8;
--color-stk26-dash: oklch(0.86 0.012 85);
--color-stk26-ink: #2a2620;
--color-stk26-ink: oklch(0.26 0.010 75);
--color-stk26-body: #3a352c;
--color-stk26-body: oklch(0.34 0.012 80);
--color-stk26-faint: #8a8474;
--color-stk26-faint: oklch(0.60 0.016 85);
--color-stk26-ring: #5b78e8;
--color-stk26-ring: oklch(0.60 0.17 262);
--font-stk26-sans: 'Archivo', system-ui, sans-serif;
--font-stk26-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-stk26: cubic-bezier(0.4, 0.9, 0.3, 1);
}
/* Older Safari and Chrome need the prefixed mask, and one class list cannot
carry two declarations of the same property. */
.stk-26__perf {
-webkit-mask: radial-gradient(9px at 50% 0, transparent 98%, #000) repeat-x 0 0 / 18px 12px;
}
```How this works
The perforation is one mask. A row of half-circles cut out of a 12px strip, tiled every 18px:
[mask:radial-gradient(9px_at_50%_0,transparent_98%,#000)_repeat-x_0_0/18px_12px]Every space is an underscore, including inside the repeat-x 0 0 position and the /18px 12px size. It looks dense, but it is the whole paper edge in one class — no SVG, no border tricks, no extra elements. Older Safari needs -webkit-mask, and since one class list cannot carry two declarations of the same property, that stays in the stylesheet.
Pseudo-content as a state indicator. The arrow direction is a genuine one-glyph difference, so it goes in content:
after:content-['_▼'] peer-checked:after:content-['_▲']Two rules in the CSS version become two utilities on the element they annotate. This is the case where content in utilities is right — unlike demo 02's LIKE/NOPE stamps, which are words and belong in the DOM.
Totals are text, not pictures. The two-column rows are flex with justify-between and every figure is real text in a monospace face, so a screen reader reads the summary in order and the numbers stay selectable and copyable.
Make it yours
- Change the unroll speed via
duration-550on the grid wrapper. - Deepen the perforation by editing the mask's
9pxradius and18px 12pxtile. - Swap the paper colour and mono face through the
@themetokens; the perforation reads the same paper colour, so they cannot drift. - Add or remove line-item rows freely — the grid reveal does not care how many there are.
- Start rolled up by removing
checkedfrom the input.
Gotchas — read before shipping
- Every space inside the mask value must be an underscore, including in the position and the
/size. One literal space and the class silently disappears. - The
-webkit-maskfallback has to stay in CSS — one class list cannot hold two declarations of the same property. - Animate
grid-template-rows, not height; and keepoverflow-hiddenon the body so it clips while rolled. - The perforation strip must be the same colour as the paper, or the cut-outs read as notches in the wrong colour.
- Label the toggle and keep the checkbox
sr-only; the whole receipt is otherwise unopenable from the keyboard. contentin a utility needs the quotes inside the brackets and no spaces — use_for the leading space.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Animating grid-template-rows needs Chrome 107+, Safari 16+, Firefox 66+ and falls back to an instant reveal; CSS mask is Chrome 120+ unprefixed with the -webkit- path covering older Safari and Chrome.