22 / 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 Washi Paper Crane Button
Handmade washi stock with fibrous speckle, a deckled bottom edge cut by a mask, and a folded paper crane whose wing lifts on hover. The button tilts on its long axis like a sheet being picked up — and the two-layer mask that tears the edge is the part that stays CSS.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<div class="tb-22 w-full min-h-svh grid place-items-center font-tb22-sans p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" class="tb-22__sheet group relative cursor-pointer inline-grid grid-flow-col items-center gap-3.5 min-h-14 pt-3.5 pb-4.5 px-8 border-0 rounded-[0.35rem] text-base font-medium tracking-[0.03em] text-tb22-ink drop-shadow-[0_10px_14px_var(--color-tb22-shadow)] transition-[rotate,translate,filter] duration-[450ms] ease-tb22-out hover:-rotate-[1.4deg] hover:-translate-y-[3px] hover:drop-shadow-[0_18px_22px_var(--color-tb22-shadow-lift)] focus-visible:-rotate-[1.4deg] focus-visible:-translate-y-[3px] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tb22-accent active:rotate-0 active:translate-y-px active:drop-shadow-[0_4px_6px_var(--color-tb22-shadow)] disabled:cursor-not-allowed disabled:opacity-55 disabled:drop-shadow-none motion-reduce:transition-none motion-reduce:hover:rotate-0 motion-reduce:hover:translate-y-0">
<span aria-hidden="true" class="relative w-6 h-[1.35rem] shrink-0 bg-tb22-accent [clip-path:polygon(0_68%,52%_8%,100%_46%,60%_100%)] transition-[rotate] duration-500 ease-tb22-out group-hover:-rotate-12 group-focus-visible:-rotate-12 motion-reduce:transition-none">
<span class="absolute inset-0 bg-tb22-accent-light [clip-path:polygon(6%_62%,52%_10%,58%_62%)] [transform-origin:52%_62%] transition-[rotate] duration-[550ms] ease-tb22-out group-hover:-rotate-[38deg] group-focus-visible:-rotate-[38deg] motion-reduce:transition-none"></span>
</span>
<span class="relative z-20">Send a note</span>
</button>
</div><div class="tb-22 w-full min-h-svh grid place-items-center font-tb22-sans p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" class="tb-22__sheet group relative cursor-pointer inline-grid grid-flow-col items-center gap-3.5 min-h-14 pt-3.5 pb-4.5 px-8 border-0 rounded-[0.35rem] text-base font-medium tracking-[0.03em] text-tb22-ink drop-shadow-[0_10px_14px_var(--color-tb22-shadow)] transition-[rotate,translate,filter] duration-[450ms] ease-tb22-out hover:-rotate-[1.4deg] hover:-translate-y-[3px] hover:drop-shadow-[0_18px_22px_var(--color-tb22-shadow-lift)] focus-visible:-rotate-[1.4deg] focus-visible:-translate-y-[3px] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tb22-accent active:rotate-0 active:translate-y-px active:drop-shadow-[0_4px_6px_var(--color-tb22-shadow)] disabled:cursor-not-allowed disabled:opacity-55 disabled:drop-shadow-none motion-reduce:transition-none motion-reduce:hover:rotate-0 motion-reduce:hover:translate-y-0">
<span aria-hidden="true" class="relative w-6 h-[1.35rem] shrink-0 bg-tb22-accent [clip-path:polygon(0_68%,52%_8%,100%_46%,60%_100%)] transition-[rotate] duration-500 ease-tb22-out group-hover:-rotate-12 group-focus-visible:-rotate-12 motion-reduce:transition-none">
<span class="absolute inset-0 bg-tb22-accent-light [clip-path:polygon(6%_62%,52%_10%,58%_62%)] [transform-origin:52%_62%] transition-[rotate] duration-[550ms] ease-tb22-out group-hover:-rotate-[38deg] group-focus-visible:-rotate-[38deg] motion-reduce:transition-none"></span>
</span>
<span class="relative z-20">Send a note</span>
</button>
</div>@import "tailwindcss";
@theme {
--color-tb22-bg: #efe9df;
--color-tb22-bg: oklch(0.935 0.012 85);
--color-tb22-paper: #fbf7f0;
--color-tb22-paper: oklch(0.975 0.01 85);
--color-tb22-fibre: #cdbfa8;
--color-tb22-fibre: oklch(0.8 0.022 78);
--color-tb22-ink: #2c2620;
--color-tb22-ink: oklch(0.26 0.012 60);
--color-tb22-accent: #a8503a;
--color-tb22-accent: oklch(0.52 0.13 35);
--color-tb22-accent-light: #c98771;
--color-tb22-accent-light: oklch(0.66 0.1 35);
--color-tb22-shadow: #2c262038;
--color-tb22-shadow: oklch(0.26 0.012 60 / 0.22);
--color-tb22-shadow-lift: #2c262047;
--color-tb22-shadow-lift: oklch(0.26 0.012 60 / 0.28);
--font-tb22-sans: "Zen Maru Gothic", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb22-out: cubic-bezier(0.16, 1, 0.3, 1);
}
.tb-22 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(50rem 30rem at 50% 20%, color-mix(in oklab, white 60%, transparent), transparent 65%), var(--color-tb22-bg);
}
/* THE SHEET: fibre speckle (three sub-pixel radial tiles) + a deckled bottom edge.
The mask is two layers and every size/repeat must be explicit — a missing
no-repeat tiles the base layer and erases the button. */
.tb-22__sheet {
background-color: var(--color-tb22-paper);
background-image: radial-gradient(.05rem .05rem at 12% 24%, color-mix(in oklab, var(--color-tb22-fibre) 45%, transparent) 99%, transparent), radial-gradient(.04rem .04rem at 63% 71%, color-mix(in oklab, var(--color-tb22-fibre) 35%, transparent) 99%, transparent), radial-gradient(.045rem .045rem at 84% 33%, color-mix(in oklab, var(--color-tb22-fibre) 30%, transparent) 99%, transparent), linear-gradient(170deg, color-mix(in oklab, white 30%, transparent), transparent 55%);
background-size: 9px 9px, 14px 14px, 19px 19px, 100% 100%;
-webkit-mask: radial-gradient(.42rem .3rem at 50% 100%, transparent 96%, #000 0) 0 100% / 1.15rem .38rem repeat-x, linear-gradient(#000, #000) 0 0 / 100% calc(100% - .37rem) no-repeat;
mask: radial-gradient(.42rem .3rem at 50% 100%, transparent 96%, #000 0) 0 100% / 1.15rem .38rem repeat-x, linear-gradient(#000, #000) 0 0 / 100% calc(100% - .37rem) no-repeat;
}
@media (prefers-color-scheme: dark) {
.tb-22:not([data-theme="light"]) {
--color-tb22-bg: oklch(0.2 0.012 60);
--color-tb22-paper: oklch(0.26 0.014 60);
--color-tb22-fibre: oklch(0.42 0.02 60);
--color-tb22-ink: oklch(0.94 0.014 80);
--color-tb22-accent: oklch(0.72 0.11 40);
--color-tb22-accent-light: oklch(0.82 0.08 40);
}
}
.tb-22[data-theme="dark"] {
--color-tb22-bg: oklch(0.2 0.012 60);
--color-tb22-paper: oklch(0.26 0.014 60);
--color-tb22-ink: oklch(0.94 0.014 80);
--color-tb22-accent: oklch(0.72 0.11 40);
}
@media (forced-colors: active) {
.tb-22__sheet {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
mask: none;
-webkit-mask: none;
filter: none;
}
.tb-22__sheet span[aria-hidden] {
background: ButtonText;
}
}@import "tailwindcss";
@theme {
--color-tb22-bg: #efe9df;
--color-tb22-bg: oklch(0.935 0.012 85);
--color-tb22-paper: #fbf7f0;
--color-tb22-paper: oklch(0.975 0.01 85);
--color-tb22-fibre: #cdbfa8;
--color-tb22-fibre: oklch(0.8 0.022 78);
--color-tb22-ink: #2c2620;
--color-tb22-ink: oklch(0.26 0.012 60);
--color-tb22-accent: #a8503a;
--color-tb22-accent: oklch(0.52 0.13 35);
--color-tb22-accent-light: #c98771;
--color-tb22-accent-light: oklch(0.66 0.1 35);
--color-tb22-shadow: #2c262038;
--color-tb22-shadow: oklch(0.26 0.012 60 / 0.22);
--color-tb22-shadow-lift: #2c262047;
--color-tb22-shadow-lift: oklch(0.26 0.012 60 / 0.28);
--font-tb22-sans: "Zen Maru Gothic", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb22-out: cubic-bezier(0.16, 1, 0.3, 1);
}
.tb-22 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(50rem 30rem at 50% 20%, color-mix(in oklab, white 60%, transparent), transparent 65%), var(--color-tb22-bg);
}
/* THE SHEET: fibre speckle (three sub-pixel radial tiles) + a deckled bottom edge.
The mask is two layers and every size/repeat must be explicit — a missing
no-repeat tiles the base layer and erases the button. */
.tb-22__sheet {
background-color: var(--color-tb22-paper);
background-image: radial-gradient(.05rem .05rem at 12% 24%, color-mix(in oklab, var(--color-tb22-fibre) 45%, transparent) 99%, transparent), radial-gradient(.04rem .04rem at 63% 71%, color-mix(in oklab, var(--color-tb22-fibre) 35%, transparent) 99%, transparent), radial-gradient(.045rem .045rem at 84% 33%, color-mix(in oklab, var(--color-tb22-fibre) 30%, transparent) 99%, transparent), linear-gradient(170deg, color-mix(in oklab, white 30%, transparent), transparent 55%);
background-size: 9px 9px, 14px 14px, 19px 19px, 100% 100%;
-webkit-mask: radial-gradient(.42rem .3rem at 50% 100%, transparent 96%, #000 0) 0 100% / 1.15rem .38rem repeat-x, linear-gradient(#000, #000) 0 0 / 100% calc(100% - .37rem) no-repeat;
mask: radial-gradient(.42rem .3rem at 50% 100%, transparent 96%, #000 0) 0 100% / 1.15rem .38rem repeat-x, linear-gradient(#000, #000) 0 0 / 100% calc(100% - .37rem) no-repeat;
}
@media (prefers-color-scheme: dark) {
.tb-22:not([data-theme="light"]) {
--color-tb22-bg: oklch(0.2 0.012 60);
--color-tb22-paper: oklch(0.26 0.014 60);
--color-tb22-fibre: oklch(0.42 0.02 60);
--color-tb22-ink: oklch(0.94 0.014 80);
--color-tb22-accent: oklch(0.72 0.11 40);
--color-tb22-accent-light: oklch(0.82 0.08 40);
}
}
.tb-22[data-theme="dark"] {
--color-tb22-bg: oklch(0.2 0.012 60);
--color-tb22-paper: oklch(0.26 0.014 60);
--color-tb22-ink: oklch(0.94 0.014 80);
--color-tb22-accent: oklch(0.72 0.11 40);
}
@media (forced-colors: active) {
.tb-22__sheet {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
mask: none;
-webkit-mask: none;
filter: none;
}
.tb-22__sheet span[aria-hidden] {
background: ButtonText;
}
}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-tb22-bg: #efe9df;
--color-tb22-bg: oklch(0.935 0.012 85);
--color-tb22-paper: #fbf7f0;
--color-tb22-paper: oklch(0.975 0.01 85);
--color-tb22-fibre: #cdbfa8;
--color-tb22-fibre: oklch(0.8 0.022 78);
--color-tb22-ink: #2c2620;
--color-tb22-ink: oklch(0.26 0.012 60);
--color-tb22-accent: #a8503a;
--color-tb22-accent: oklch(0.52 0.13 35);
--color-tb22-accent-light: #c98771;
--color-tb22-accent-light: oklch(0.66 0.1 35);
--color-tb22-shadow: #2c262038;
--color-tb22-shadow: oklch(0.26 0.012 60 / 0.22);
--color-tb22-shadow-lift: #2c262047;
--color-tb22-shadow-lift: oklch(0.26 0.012 60 / 0.28);
--font-tb22-sans: "Zen Maru Gothic", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb22-out: cubic-bezier(0.16, 1, 0.3, 1);
}
.tb-22 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(50rem 30rem at 50% 20%, color-mix(in oklab, white 60%, transparent), transparent 65%), var(--color-tb22-bg);
}
/* THE SHEET: fibre speckle (three sub-pixel radial tiles) + a deckled bottom edge.
The mask is two layers and every size/repeat must be explicit — a missing
no-repeat tiles the base layer and erases the button. */
.tb-22__sheet {
background-color: var(--color-tb22-paper);
background-image: radial-gradient(.05rem .05rem at 12% 24%, color-mix(in oklab, var(--color-tb22-fibre) 45%, transparent) 99%, transparent), radial-gradient(.04rem .04rem at 63% 71%, color-mix(in oklab, var(--color-tb22-fibre) 35%, transparent) 99%, transparent), radial-gradient(.045rem .045rem at 84% 33%, color-mix(in oklab, var(--color-tb22-fibre) 30%, transparent) 99%, transparent), linear-gradient(170deg, color-mix(in oklab, white 30%, transparent), transparent 55%);
background-size: 9px 9px, 14px 14px, 19px 19px, 100% 100%;
-webkit-mask: radial-gradient(.42rem .3rem at 50% 100%, transparent 96%, #000 0) 0 100% / 1.15rem .38rem repeat-x, linear-gradient(#000, #000) 0 0 / 100% calc(100% - .37rem) no-repeat;
mask: radial-gradient(.42rem .3rem at 50% 100%, transparent 96%, #000 0) 0 100% / 1.15rem .38rem repeat-x, linear-gradient(#000, #000) 0 0 / 100% calc(100% - .37rem) no-repeat;
}
@media (prefers-color-scheme: dark) {
.tb-22:not([data-theme="light"]) {
--color-tb22-bg: oklch(0.2 0.012 60);
--color-tb22-paper: oklch(0.26 0.014 60);
--color-tb22-fibre: oklch(0.42 0.02 60);
--color-tb22-ink: oklch(0.94 0.014 80);
--color-tb22-accent: oklch(0.72 0.11 40);
--color-tb22-accent-light: oklch(0.82 0.08 40);
}
}
.tb-22[data-theme="dark"] {
--color-tb22-bg: oklch(0.2 0.012 60);
--color-tb22-paper: oklch(0.26 0.014 60);
--color-tb22-ink: oklch(0.94 0.014 80);
--color-tb22-accent: oklch(0.72 0.11 40);
}
@media (forced-colors: active) {
.tb-22__sheet {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
mask: none;
-webkit-mask: none;
filter: none;
}
.tb-22__sheet span[aria-hidden] {
background: ButtonText;
}
}@import "tailwindcss";
@theme {
--color-tb22-bg: #efe9df;
--color-tb22-bg: oklch(0.935 0.012 85);
--color-tb22-paper: #fbf7f0;
--color-tb22-paper: oklch(0.975 0.01 85);
--color-tb22-fibre: #cdbfa8;
--color-tb22-fibre: oklch(0.8 0.022 78);
--color-tb22-ink: #2c2620;
--color-tb22-ink: oklch(0.26 0.012 60);
--color-tb22-accent: #a8503a;
--color-tb22-accent: oklch(0.52 0.13 35);
--color-tb22-accent-light: #c98771;
--color-tb22-accent-light: oklch(0.66 0.1 35);
--color-tb22-shadow: #2c262038;
--color-tb22-shadow: oklch(0.26 0.012 60 / 0.22);
--color-tb22-shadow-lift: #2c262047;
--color-tb22-shadow-lift: oklch(0.26 0.012 60 / 0.28);
--font-tb22-sans: "Zen Maru Gothic", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb22-out: cubic-bezier(0.16, 1, 0.3, 1);
}
.tb-22 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(50rem 30rem at 50% 20%, color-mix(in oklab, white 60%, transparent), transparent 65%), var(--color-tb22-bg);
}
/* THE SHEET: fibre speckle (three sub-pixel radial tiles) + a deckled bottom edge.
The mask is two layers and every size/repeat must be explicit — a missing
no-repeat tiles the base layer and erases the button. */
.tb-22__sheet {
background-color: var(--color-tb22-paper);
background-image: radial-gradient(.05rem .05rem at 12% 24%, color-mix(in oklab, var(--color-tb22-fibre) 45%, transparent) 99%, transparent), radial-gradient(.04rem .04rem at 63% 71%, color-mix(in oklab, var(--color-tb22-fibre) 35%, transparent) 99%, transparent), radial-gradient(.045rem .045rem at 84% 33%, color-mix(in oklab, var(--color-tb22-fibre) 30%, transparent) 99%, transparent), linear-gradient(170deg, color-mix(in oklab, white 30%, transparent), transparent 55%);
background-size: 9px 9px, 14px 14px, 19px 19px, 100% 100%;
-webkit-mask: radial-gradient(.42rem .3rem at 50% 100%, transparent 96%, #000 0) 0 100% / 1.15rem .38rem repeat-x, linear-gradient(#000, #000) 0 0 / 100% calc(100% - .37rem) no-repeat;
mask: radial-gradient(.42rem .3rem at 50% 100%, transparent 96%, #000 0) 0 100% / 1.15rem .38rem repeat-x, linear-gradient(#000, #000) 0 0 / 100% calc(100% - .37rem) no-repeat;
}
@media (prefers-color-scheme: dark) {
.tb-22:not([data-theme="light"]) {
--color-tb22-bg: oklch(0.2 0.012 60);
--color-tb22-paper: oklch(0.26 0.014 60);
--color-tb22-fibre: oklch(0.42 0.02 60);
--color-tb22-ink: oklch(0.94 0.014 80);
--color-tb22-accent: oklch(0.72 0.11 40);
--color-tb22-accent-light: oklch(0.82 0.08 40);
}
}
.tb-22[data-theme="dark"] {
--color-tb22-bg: oklch(0.2 0.012 60);
--color-tb22-paper: oklch(0.26 0.014 60);
--color-tb22-ink: oklch(0.94 0.014 80);
--color-tb22-accent: oklch(0.72 0.11 40);
}
@media (forced-colors: active) {
.tb-22__sheet {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
mask: none;
-webkit-mask: none;
filter: none;
}
.tb-22__sheet span[aria-hidden] {
background: ButtonText;
}
}<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=Zen+Maru+Gothic: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=Zen+Maru+Gothic:wght@400;500;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Utilities: the tilt and lift, the filter shadows, both crane polygons, the wing hinge and rotation, every state. CSS: the fibre speckle and the two-layer deckle mask.
drop-shadow-*rather thanshadow-*is the load-bearing choice: a box shadow would draw a straight rectangle under a torn edge. The filter follows the mask, so the shadow is deckled too.- The split here is the same as the brass plate, and it is becoming the collection's rule: shape and motion port to utilities; surface texture stays CSS. Both demos put a texture warning in the stylesheet comment rather than in prose, where the person editing it will actually read it.
- A11y fix from the CSS original: the crane's accent was
oklch(0.55 0.13 35)and its wingcolor-mix(accent 65%, white)≈oklch(0.72)— the wing against the paper measured about 1.9:1, so the fold was invisible to low-vision users at rest. The port darkens the body to0.52and names the wing--color-tb22-accent-lightat0.66(≈3.2:1 against the paper), enough for a non-text graphic.
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 Washi Paper Crane Button
Source: https://codefronts.com/components/tailwind-css-buttons/tailwind-washi-paper-crane-button/
Handmade washi stock with fibrous speckle, a deckled bottom edge cut by a mask, and a folded paper crane whose wing lifts on hover. The button tilts on its long axis like a sheet being picked up — and the two-layer mask that tears the edge is the part that stays CSS.
## HTML
```html
<div class="tb-22 w-full min-h-svh grid place-items-center font-tb22-sans p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" class="tb-22__sheet group relative cursor-pointer inline-grid grid-flow-col items-center gap-3.5 min-h-14 pt-3.5 pb-4.5 px-8 border-0 rounded-[0.35rem] text-base font-medium tracking-[0.03em] text-tb22-ink drop-shadow-[0_10px_14px_var(--color-tb22-shadow)] transition-[rotate,translate,filter] duration-[450ms] ease-tb22-out hover:-rotate-[1.4deg] hover:-translate-y-[3px] hover:drop-shadow-[0_18px_22px_var(--color-tb22-shadow-lift)] focus-visible:-rotate-[1.4deg] focus-visible:-translate-y-[3px] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tb22-accent active:rotate-0 active:translate-y-px active:drop-shadow-[0_4px_6px_var(--color-tb22-shadow)] disabled:cursor-not-allowed disabled:opacity-55 disabled:drop-shadow-none motion-reduce:transition-none motion-reduce:hover:rotate-0 motion-reduce:hover:translate-y-0">
<span aria-hidden="true" class="relative w-6 h-[1.35rem] shrink-0 bg-tb22-accent [clip-path:polygon(0_68%,52%_8%,100%_46%,60%_100%)] transition-[rotate] duration-500 ease-tb22-out group-hover:-rotate-12 group-focus-visible:-rotate-12 motion-reduce:transition-none">
<span class="absolute inset-0 bg-tb22-accent-light [clip-path:polygon(6%_62%,52%_10%,58%_62%)] [transform-origin:52%_62%] transition-[rotate] duration-[550ms] ease-tb22-out group-hover:-rotate-[38deg] group-focus-visible:-rotate-[38deg] motion-reduce:transition-none"></span>
</span>
<span class="relative z-20">Send a note</span>
</button>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tb22-bg: #efe9df;
--color-tb22-bg: oklch(0.935 0.012 85);
--color-tb22-paper: #fbf7f0;
--color-tb22-paper: oklch(0.975 0.01 85);
--color-tb22-fibre: #cdbfa8;
--color-tb22-fibre: oklch(0.8 0.022 78);
--color-tb22-ink: #2c2620;
--color-tb22-ink: oklch(0.26 0.012 60);
--color-tb22-accent: #a8503a;
--color-tb22-accent: oklch(0.52 0.13 35);
--color-tb22-accent-light: #c98771;
--color-tb22-accent-light: oklch(0.66 0.1 35);
--color-tb22-shadow: #2c262038;
--color-tb22-shadow: oklch(0.26 0.012 60 / 0.22);
--color-tb22-shadow-lift: #2c262047;
--color-tb22-shadow-lift: oklch(0.26 0.012 60 / 0.28);
--font-tb22-sans: "Zen Maru Gothic", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb22-out: cubic-bezier(0.16, 1, 0.3, 1);
}
.tb-22 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(50rem 30rem at 50% 20%, color-mix(in oklab, white 60%, transparent), transparent 65%), var(--color-tb22-bg);
}
/* THE SHEET: fibre speckle (three sub-pixel radial tiles) + a deckled bottom edge.
The mask is two layers and every size/repeat must be explicit — a missing
no-repeat tiles the base layer and erases the button. */
.tb-22__sheet {
background-color: var(--color-tb22-paper);
background-image: radial-gradient(.05rem .05rem at 12% 24%, color-mix(in oklab, var(--color-tb22-fibre) 45%, transparent) 99%, transparent), radial-gradient(.04rem .04rem at 63% 71%, color-mix(in oklab, var(--color-tb22-fibre) 35%, transparent) 99%, transparent), radial-gradient(.045rem .045rem at 84% 33%, color-mix(in oklab, var(--color-tb22-fibre) 30%, transparent) 99%, transparent), linear-gradient(170deg, color-mix(in oklab, white 30%, transparent), transparent 55%);
background-size: 9px 9px, 14px 14px, 19px 19px, 100% 100%;
-webkit-mask: radial-gradient(.42rem .3rem at 50% 100%, transparent 96%, #000 0) 0 100% / 1.15rem .38rem repeat-x, linear-gradient(#000, #000) 0 0 / 100% calc(100% - .37rem) no-repeat;
mask: radial-gradient(.42rem .3rem at 50% 100%, transparent 96%, #000 0) 0 100% / 1.15rem .38rem repeat-x, linear-gradient(#000, #000) 0 0 / 100% calc(100% - .37rem) no-repeat;
}
@media (prefers-color-scheme: dark) {
.tb-22:not([data-theme="light"]) {
--color-tb22-bg: oklch(0.2 0.012 60);
--color-tb22-paper: oklch(0.26 0.014 60);
--color-tb22-fibre: oklch(0.42 0.02 60);
--color-tb22-ink: oklch(0.94 0.014 80);
--color-tb22-accent: oklch(0.72 0.11 40);
--color-tb22-accent-light: oklch(0.82 0.08 40);
}
}
.tb-22[data-theme="dark"] {
--color-tb22-bg: oklch(0.2 0.012 60);
--color-tb22-paper: oklch(0.26 0.014 60);
--color-tb22-ink: oklch(0.94 0.014 80);
--color-tb22-accent: oklch(0.72 0.11 40);
}
@media (forced-colors: active) {
.tb-22__sheet {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
mask: none;
-webkit-mask: none;
filter: none;
}
.tb-22__sheet span[aria-hidden] {
background: ButtonText;
}
}
```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 Washi Paper Crane Button
Source: https://codefronts.com/components/tailwind-css-buttons/tailwind-washi-paper-crane-button/
Handmade washi stock with fibrous speckle, a deckled bottom edge cut by a mask, and a folded paper crane whose wing lifts on hover. The button tilts on its long axis like a sheet being picked up — and the two-layer mask that tears the edge is the part that stays CSS.
## HTML
```html
<div class="tb-22 w-full min-h-svh grid place-items-center font-tb22-sans p-[clamp(1.5rem,6vw,4rem)]">
<button type="button" class="tb-22__sheet group relative cursor-pointer inline-grid grid-flow-col items-center gap-3.5 min-h-14 pt-3.5 pb-4.5 px-8 border-0 rounded-[0.35rem] text-base font-medium tracking-[0.03em] text-tb22-ink drop-shadow-[0_10px_14px_var(--color-tb22-shadow)] transition-[rotate,translate,filter] duration-[450ms] ease-tb22-out hover:-rotate-[1.4deg] hover:-translate-y-[3px] hover:drop-shadow-[0_18px_22px_var(--color-tb22-shadow-lift)] focus-visible:-rotate-[1.4deg] focus-visible:-translate-y-[3px] focus-visible:outline-2 focus-visible:outline-offset-[5px] focus-visible:outline-tb22-accent active:rotate-0 active:translate-y-px active:drop-shadow-[0_4px_6px_var(--color-tb22-shadow)] disabled:cursor-not-allowed disabled:opacity-55 disabled:drop-shadow-none motion-reduce:transition-none motion-reduce:hover:rotate-0 motion-reduce:hover:translate-y-0">
<span aria-hidden="true" class="relative w-6 h-[1.35rem] shrink-0 bg-tb22-accent [clip-path:polygon(0_68%,52%_8%,100%_46%,60%_100%)] transition-[rotate] duration-500 ease-tb22-out group-hover:-rotate-12 group-focus-visible:-rotate-12 motion-reduce:transition-none">
<span class="absolute inset-0 bg-tb22-accent-light [clip-path:polygon(6%_62%,52%_10%,58%_62%)] [transform-origin:52%_62%] transition-[rotate] duration-[550ms] ease-tb22-out group-hover:-rotate-[38deg] group-focus-visible:-rotate-[38deg] motion-reduce:transition-none"></span>
</span>
<span class="relative z-20">Send a note</span>
</button>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tb22-bg: #efe9df;
--color-tb22-bg: oklch(0.935 0.012 85);
--color-tb22-paper: #fbf7f0;
--color-tb22-paper: oklch(0.975 0.01 85);
--color-tb22-fibre: #cdbfa8;
--color-tb22-fibre: oklch(0.8 0.022 78);
--color-tb22-ink: #2c2620;
--color-tb22-ink: oklch(0.26 0.012 60);
--color-tb22-accent: #a8503a;
--color-tb22-accent: oklch(0.52 0.13 35);
--color-tb22-accent-light: #c98771;
--color-tb22-accent-light: oklch(0.66 0.1 35);
--color-tb22-shadow: #2c262038;
--color-tb22-shadow: oklch(0.26 0.012 60 / 0.22);
--color-tb22-shadow-lift: #2c262047;
--color-tb22-shadow-lift: oklch(0.26 0.012 60 / 0.28);
--font-tb22-sans: "Zen Maru Gothic", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--ease-tb22-out: cubic-bezier(0.16, 1, 0.3, 1);
}
.tb-22 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
background: radial-gradient(50rem 30rem at 50% 20%, color-mix(in oklab, white 60%, transparent), transparent 65%), var(--color-tb22-bg);
}
/* THE SHEET: fibre speckle (three sub-pixel radial tiles) + a deckled bottom edge.
The mask is two layers and every size/repeat must be explicit — a missing
no-repeat tiles the base layer and erases the button. */
.tb-22__sheet {
background-color: var(--color-tb22-paper);
background-image: radial-gradient(.05rem .05rem at 12% 24%, color-mix(in oklab, var(--color-tb22-fibre) 45%, transparent) 99%, transparent), radial-gradient(.04rem .04rem at 63% 71%, color-mix(in oklab, var(--color-tb22-fibre) 35%, transparent) 99%, transparent), radial-gradient(.045rem .045rem at 84% 33%, color-mix(in oklab, var(--color-tb22-fibre) 30%, transparent) 99%, transparent), linear-gradient(170deg, color-mix(in oklab, white 30%, transparent), transparent 55%);
background-size: 9px 9px, 14px 14px, 19px 19px, 100% 100%;
-webkit-mask: radial-gradient(.42rem .3rem at 50% 100%, transparent 96%, #000 0) 0 100% / 1.15rem .38rem repeat-x, linear-gradient(#000, #000) 0 0 / 100% calc(100% - .37rem) no-repeat;
mask: radial-gradient(.42rem .3rem at 50% 100%, transparent 96%, #000 0) 0 100% / 1.15rem .38rem repeat-x, linear-gradient(#000, #000) 0 0 / 100% calc(100% - .37rem) no-repeat;
}
@media (prefers-color-scheme: dark) {
.tb-22:not([data-theme="light"]) {
--color-tb22-bg: oklch(0.2 0.012 60);
--color-tb22-paper: oklch(0.26 0.014 60);
--color-tb22-fibre: oklch(0.42 0.02 60);
--color-tb22-ink: oklch(0.94 0.014 80);
--color-tb22-accent: oklch(0.72 0.11 40);
--color-tb22-accent-light: oklch(0.82 0.08 40);
}
}
.tb-22[data-theme="dark"] {
--color-tb22-bg: oklch(0.2 0.012 60);
--color-tb22-paper: oklch(0.26 0.014 60);
--color-tb22-ink: oklch(0.94 0.014 80);
--color-tb22-accent: oklch(0.72 0.11 40);
}
@media (forced-colors: active) {
.tb-22__sheet {
background: ButtonFace;
color: ButtonText;
border: 1px solid ButtonText;
mask: none;
-webkit-mask: none;
filter: none;
}
.tb-22__sheet span[aria-hidden] {
background: ButtonText;
}
}
```How this works
The deckled edge is a two-layer mask — repeating soft notches along the bottom, plus a solid block for everything above it. Multi-layer mask shorthands need each layer's size and repeat spelled out, and a single missing no-repeat tiles the base layer and eats the button. That is machinery, and it stays in the stylesheet with the warning attached.
The fibre speckle stays CSS too: three sub-pixel radial gradients at 9px/14px/19px tiles. Same reasoning as the brass grain — a texture frequency stack is not a design value.
The crane is entirely utilities. Two clip-path polygons, a hinge origin, two rotations, one hover state. This is the split the collection keeps arriving at: shape and motion port cleanly, surface does not.
Make it yours
- Retint with
--color-tb22-paperand--color-tb22-fibre— indigo, persimmon and moss all work on the same recipe. - Deckle rhythm is the mask's
1.15remtile width; wider reads as heavier stock. - Rotate the crane's rest angle for a folded-flat look, or delete it and keep the paper.
- Tri-fold letter: add a low-alpha linear gradient at 40% width as a second fold shadow.
- Keep the tilt under 2°. This is the one number in the demo that is not taste.
Gotchas — read before shipping
- Multi-layer mask shorthands need explicit size and repeat per layer. A missing
no-repeatsilently destroys the button. - Soft paper palettes drift under 4.5:1 fast. The ink here is a real near-black, not a warm grey.
- Micro radial-gradient speckle is fine as a static background and must never be animated — animate a separate overlay.
transform-originon a clipped element refers to its border box, not the visible shape. Expect to nudge by eye.drop-shadow-*follows the mask;shadow-*would draw a rectangle under the torn edge. Use the filter here.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 120+ | 16.4+ | 128+ | 120+ |
Multi-layer mask shorthand needs Chrome 120 / Safari 15.4 / Firefox 53, with the -webkit- shim in the CSS block. Without it the deckled edge falls back to a straight edge and nothing else changes.