51 CSS Buttons22 / 51

Pure CSSMIT licensed

CSS Washi Paper Crane Button

Handmade washi stock with fibrous speckle, a deckled bottom edge cut by a mask, and a folded paper wing that lifts off the corner on hover — the crane's fold lines are a single conic-gradient shading pass, and the whole button tilts on its long axis like a sheet being picked up.

Published Updated

Live Demo
Try it

The code

<div class="cb-22">
  <div class="cb-22__stage">
    <button class="cb-22__btn" type="button">
      <span class="cb-22__crane" aria-hidden="true"><span class="cb-22__wing"></span></span>
      <span class="cb-22__label">Send a note</span>
    </button>
  </div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Zen+Maru+Gothic:wght@400;500;700&display=swap');

.cb-22 {
  --bg: #efe9df;
  --paper: #fbf7f0;
  --fibre: #cdbfa8;
  --ink: #2c2620;
  --accent: #b4573f;
  --radius: .35rem;
  --sans: "Zen Maru Gothic",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: radial-gradient(50rem 30rem at 50% 20%, color-mix(in oklab,white 60%,transparent), transparent 65%), var(--bg);
  font-family: var(--sans);
}

@supports (color: oklch(50% 0 0)) {
  .cb-22 {
    --bg: oklch(93.5% .012 85);
    --paper: oklch(97.5% .01 85);
    --fibre: oklch(80% .022 78);
    --accent: oklch(55% .13 35);
  }
}

@media (prefers-color-scheme: dark) {
  .cb-22:not([data-theme="light"]) {
    --bg: #1b1815;
    --paper: #272220;
    --fibre: #463d34;
    --ink: #f2ece2;
    --accent: #e08a6f;
  }
}

.cb-22[data-theme="dark"] {
  --bg: #1b1815;
  --paper: #272220;
  --fibre: #463d34;
  --ink: #f2ece2;
  --accent: #e08a6f;
}

.cb-22,
.cb-22 *,
.cb-22 *::before,
.cb-22 *::after {
  box-sizing: border-box;
}

.cb-22__stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(1.5rem,6vw,4rem);
}

.cb-22__btn {
  position: relative;
  cursor: pointer;
  display: grid;
  grid-auto-flow: column;
  align-items: center;
  gap: .85rem;
  min-height: 3.5rem;
  padding: .9rem 1.9rem 1.1rem;
  border: 0;
  border-radius: var(--radius);
  font: inherit;
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: .03em;
  color: var(--ink);
  background-color: var(--paper);
  background-image: radial-gradient(.05rem .05rem at 12% 24%, color-mix(in oklab,var(--fibre) 45%,transparent) 99%, transparent), radial-gradient(.04rem .04rem at 63% 71%, color-mix(in oklab,var(--fibre) 35%,transparent) 99%, transparent), radial-gradient(.045rem .045rem at 84% 33%, color-mix(in oklab,var(--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;
  filter: drop-shadow(0 10px 14px color-mix(in oklab,var(--ink) 22%,transparent));
  transition: rotate .45s cubic-bezier(.16,1,.3,1), translate .45s cubic-bezier(.16,1,.3,1), filter .35s ease;
}

.cb-22__label {
  position: relative;
  z-index: 2;
}

.cb-22__crane {
  position: relative;
  width: 1.5rem;
  height: 1.35rem;
  flex: none;
  background: var(--accent);
  clip-path: polygon(0 68%,52% 8%,100% 46%,60% 100%);
  transition: rotate .5s cubic-bezier(.16,1,.3,1);
}

.cb-22__wing {
  position: absolute;
  inset: 0;
  background: color-mix(in oklab,var(--accent) 65%,white);
  clip-path: polygon(6% 62%,52% 10%,58% 62%);
  transform-origin: 52% 62%;
  transition: rotate .55s cubic-bezier(.16,1,.3,1);
}

.cb-22__btn:is(:hover,:focus-visible) {
  rotate: -1.4deg;
  translate: 0 -3px;
  filter: drop-shadow(0 18px 22px color-mix(in oklab,var(--ink) 28%,transparent));
}

.cb-22__btn:is(:hover,:focus-visible) .cb-22__crane {
  rotate: -12deg;
}

.cb-22__btn:is(:hover,:focus-visible) .cb-22__wing {
  rotate: -38deg;
}

.cb-22__btn:active {
  rotate: 0deg;
  translate: 0 1px;
  filter: drop-shadow(0 4px 6px color-mix(in oklab,var(--ink) 25%,transparent));
}

.cb-22__btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 5px;
}

.cb-22__btn:disabled {
  cursor: not-allowed;
  filter: none;
  opacity: .55;
}

@media (prefers-reduced-motion: reduce) {
  .cb-22__btn,
    .cb-22__crane,
    .cb-22__wing {
    transition: none;
  }
}

@media (forced-colors: active) {
  .cb-22__btn {
    background: ButtonFace;
    color: ButtonText;
    border: 1px solid ButtonText;
    mask: none;
    -webkit-mask: none;
    filter: none;
  }

  .cb-22__crane {
    background: ButtonText;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Button 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: CSS Washi Paper Crane Button
Source: https://codefronts.com/components/css-buttons/css-washi-paper-crane-button/

Handmade washi stock with fibrous speckle, a deckled bottom edge cut by a mask, and a folded paper wing that lifts off the corner on hover — the crane's fold lines are a single conic-gradient shading pass, and the whole button tilts on its long axis like a sheet being picked up.
## HTML
```html
<div class="cb-22">
  <div class="cb-22__stage">
    <button class="cb-22__btn" type="button">
      <span class="cb-22__crane" aria-hidden="true"><span class="cb-22__wing"></span></span>
      <span class="cb-22__label">Send a note</span>
    </button>
  </div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Zen+Maru+Gothic:wght@400;500;700&display=swap');

.cb-22 {
  --bg: #efe9df;
  --paper: #fbf7f0;
  --fibre: #cdbfa8;
  --ink: #2c2620;
  --accent: #b4573f;
  --radius: .35rem;
  --sans: "Zen Maru Gothic",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: radial-gradient(50rem 30rem at 50% 20%, color-mix(in oklab,white 60%,transparent), transparent 65%), var(--bg);
  font-family: var(--sans);
}

@supports (color: oklch(50% 0 0)) {
  .cb-22 {
    --bg: oklch(93.5% .012 85);
    --paper: oklch(97.5% .01 85);
    --fibre: oklch(80% .022 78);
    --accent: oklch(55% .13 35);
  }
}

@media (prefers-color-scheme: dark) {
  .cb-22:not([data-theme="light"]) {
    --bg: #1b1815;
    --paper: #272220;
    --fibre: #463d34;
    --ink: #f2ece2;
    --accent: #e08a6f;
  }
}

.cb-22[data-theme="dark"] {
  --bg: #1b1815;
  --paper: #272220;
  --fibre: #463d34;
  --ink: #f2ece2;
  --accent: #e08a6f;
}

.cb-22,
.cb-22 *,
.cb-22 *::before,
.cb-22 *::after {
  box-sizing: border-box;
}

.cb-22__stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(1.5rem,6vw,4rem);
}

.cb-22__btn {
  position: relative;
  cursor: pointer;
  display: grid;
  grid-auto-flow: column;
  align-items: center;
  gap: .85rem;
  min-height: 3.5rem;
  padding: .9rem 1.9rem 1.1rem;
  border: 0;
  border-radius: var(--radius);
  font: inherit;
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: .03em;
  color: var(--ink);
  background-color: var(--paper);
  background-image: radial-gradient(.05rem .05rem at 12% 24%, color-mix(in oklab,var(--fibre) 45%,transparent) 99%, transparent), radial-gradient(.04rem .04rem at 63% 71%, color-mix(in oklab,var(--fibre) 35%,transparent) 99%, transparent), radial-gradient(.045rem .045rem at 84% 33%, color-mix(in oklab,var(--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;
  filter: drop-shadow(0 10px 14px color-mix(in oklab,var(--ink) 22%,transparent));
  transition: rotate .45s cubic-bezier(.16,1,.3,1), translate .45s cubic-bezier(.16,1,.3,1), filter .35s ease;
}

.cb-22__label {
  position: relative;
  z-index: 2;
}

.cb-22__crane {
  position: relative;
  width: 1.5rem;
  height: 1.35rem;
  flex: none;
  background: var(--accent);
  clip-path: polygon(0 68%,52% 8%,100% 46%,60% 100%);
  transition: rotate .5s cubic-bezier(.16,1,.3,1);
}

.cb-22__wing {
  position: absolute;
  inset: 0;
  background: color-mix(in oklab,var(--accent) 65%,white);
  clip-path: polygon(6% 62%,52% 10%,58% 62%);
  transform-origin: 52% 62%;
  transition: rotate .55s cubic-bezier(.16,1,.3,1);
}

.cb-22__btn:is(:hover,:focus-visible) {
  rotate: -1.4deg;
  translate: 0 -3px;
  filter: drop-shadow(0 18px 22px color-mix(in oklab,var(--ink) 28%,transparent));
}

.cb-22__btn:is(:hover,:focus-visible) .cb-22__crane {
  rotate: -12deg;
}

.cb-22__btn:is(:hover,:focus-visible) .cb-22__wing {
  rotate: -38deg;
}

.cb-22__btn:active {
  rotate: 0deg;
  translate: 0 1px;
  filter: drop-shadow(0 4px 6px color-mix(in oklab,var(--ink) 25%,transparent));
}

.cb-22__btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 5px;
}

.cb-22__btn:disabled {
  cursor: not-allowed;
  filter: none;
  opacity: .55;
}

@media (prefers-reduced-motion: reduce) {
  .cb-22__btn,
    .cb-22__crane,
    .cb-22__wing {
    transition: none;
  }
}

@media (forced-colors: active) {
  .cb-22__btn {
    background: ButtonFace;
    color: ButtonText;
    border: 1px solid ButtonText;
    mask: none;
    -webkit-mask: none;
    filter: none;
  }

  .cb-22__crane {
    background: ButtonText;
  }
}
```

How this works

The fibrous texture is many tiny radial gradients at prime-ish offsets — cheaper and crisper than an image, and it tiles at any size:

background-image:
  radial-gradient(.09rem .09rem at 12% 24%, var(--fibre) 99%, transparent),
  radial-gradient(.07rem .07rem at 63% 71%, var(--fibre) 99%, transparent),
  radial-gradient(.08rem .08rem at 84% 33%, var(--fibre) 99%, transparent);
background-size:7px 7px, 11px 11px, 13px 13px;

The deckled edge is a mask of repeating soft radial notches, so the paper appears hand-torn rather than cut:

mask:radial-gradient(.4rem .28rem at 50% 100%, transparent 96%, #000 0) 0 100%/1.1rem .35rem repeat-x,
     linear-gradient(#000,#000) 0 0/100% calc(100% - .34rem) no-repeat;

The crane is two triangles from clip-path:polygon() sharing one element: the body sits flat, and the wing rotates around its spine on hover using rotate with a transform-origin at the fold. A rotate3d-free tilt on the button itself keeps the composite cheap.

Make it yours

  • Retint with --paper and --fibre — indigo, persimmon and moss all work with the same texture recipe.
  • Change the deckle rhythm with the mask's 1.1rem tile width; wider reads as heavier stock.
  • Rotate the crane's rest angle for a folded-flat look, or delete it and keep the paper.
  • Add a second fold shadow with a low-alpha linear gradient at 40% width for a tri-fold letter feel.

Gotchas — read before shipping

  • Multi-stop mask shorthands need each layer's size and repeat spelled out; a missing no-repeat silently tiles the base layer and eats the button.
  • Soft paper palettes drift under 4.5:1 fast — the ink here is a real near-black, not a warm grey.
  • Dozens of micro radial gradients are fine as a static background but must never be animated; animate a separate overlay instead.
  • transform-origin on a clipped element refers to its border box, not the visible shape — expect to nudge the value by eye.
  • Keep the tilt under 2°: paper that rotates further stops looking like a button and starts looking broken.

Browser support

ChromeSafariFirefoxEdge
120+15.4+53+120+

mask shorthand with multiple layers: Chrome 120 / Safari 15.4 / Firefox 53. Without it the deckled edge falls back to a straight edge — the button is otherwise identical.

Techniques used in this demo

Search CodeFronts

Loading…