Pure CSSMIT licensed

Clay Button Press and Squish States

Five clay buttons with a real physical press: on :active the object squashes on the Y axis, the drop shadow collapses, and the inset layers invert so the surface genuinely looks depressed. Includes the animator's squash-and-stretch release — the button overshoots slightly on the way back up — plus an icon button, a pill, a destructive variant and a disabled state, all keyboard-operable.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: a 10,000+ component Figma UI kit · from $129 one-time

Untitled UI · Affiliate link

Get Untitled UI Pro

The code

<section class="cl-09" aria-label="Clay button press and squish states demo">
  <div class="cl-09__stage">
    <header class="cl-09__head">
      <h2 class="cl-09__h">Press it. It squishes.</h2>
      <p class="cl-09__lede">Squash on Y, sink 4px, invert both insets — then release on a springy curve. Hold a button down to feel the three changes.</p>
    </header>
    <div class="cl-09__row">
      <button class="cl-09__btn cl-09__btn--primary" type="button">Start lesson</button>
      <button class="cl-09__btn" type="button">Save draft</button>
      <button class="cl-09__btn cl-09__btn--pill" type="button">Follow</button>
      <button class="cl-09__btn cl-09__btn--icon" type="button" aria-label="Add to favourites">
        <svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true"><path d="M12 20.3l-1.4-1.3C6 14.9 3.5 12.6 3.5 9.6A4.1 4.1 0 017.6 5.5c1.5 0 2.9.7 3.8 1.9.9-1.2 2.3-1.9 3.8-1.9a4.1 4.1 0 014.1 4.1c0 3-2.5 5.3-7.1 9.4z" fill="currentColor"/></svg>
      </button>
      <button class="cl-09__btn cl-09__btn--danger" type="button">Delete</button>
      <button class="cl-09__btn cl-09__btn--off" type="button" disabled aria-disabled="true">Unavailable</button>
    </div>
    <p class="cl-09__chip" aria-hidden="true">scale 1 .93 · translate 0 4px · insets inverted · overshoot on release</p>
  </div>
</section>
.cl-09,
.cl-09 *,
.cl-09 *::before,
.cl-09 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cl-09 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--cl-09-bg);
  --cl-09-bg: oklch(0.94 0.03 165);
  --cl-09-clay: oklch(0.88 0.06 170);
  --cl-09-accent: oklch(0.82 0.13 155);
  --cl-09-danger: oklch(0.79 0.14 25);
  --cl-09-shade: oklch(0.55 0.09 170/.55);
  --cl-09-light: oklch(1 0.015 170/.95);
  --cl-09-ink: oklch(0.28 0.05 170);
  --cl-09-mut: oklch(0.5 0.04 170);
  font-family: system-ui,-apple-system,'Segoe UI',sans-serif;
  color: var(--cl-09-ink);
}

.cl-09__stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  align-content: center;
  gap: clamp(24px,4vw,40px);
  padding: clamp(20px,5vw,58px);
  background: radial-gradient(75% 55% at 50% 5%,oklch(0.97 0.035 165),transparent 65%);
}

.cl-09__head {
  text-align: center;
  max-width: 56ch;
  display: grid;
  gap: 9px;
}

.cl-09__h {
  font-size: clamp(24px,3.8vw,34px);
  font-weight: 780;
  letter-spacing: -.02em;
  text-wrap: balance;
}

.cl-09__lede {
  font-size: 15px;
  line-height: 1.6;
  color: var(--cl-09-mut);
  text-wrap: pretty;
}

.cl-09__row {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(12px,2vw,20px);
  justify-content: center;
  align-items: center;
  width: min(100%,880px);
}

.cl-09__btn {
  --cl-09-sq: 1;
  display: grid;
  place-items: center;
  min-height: 58px;
  padding: 0 30px;
  border: 0;
  border-radius: 26px;
  font: inherit;
  font-size: 15px;
  font-weight: 680;
  letter-spacing: -.005em;
  color: var(--cl-09-ink);
  cursor: pointer;
  background: var(--cl-09-clay);
  scale: 1 var(--cl-09-sq);
  box-shadow: 0 18px 30px -10px var(--cl-09-shade),inset 0 -14px 22px -10px var(--cl-09-shade),inset 0 16px 22px -8px var(--cl-09-light);
  transition: scale .2s cubic-bezier(.2,.8,.3,1.5),translate .2s cubic-bezier(.2,.8,.3,1.5),box-shadow .2s ease,filter .25s;
}

.cl-09__btn:hover {
  translate: 0 -3px;
  filter: brightness(1.03);
}

.cl-09__btn:active {
  --cl-09-sq: .93;
  translate: 0 4px;
  filter: none;
  box-shadow: inset 0 14px 20px -8px var(--cl-09-shade),inset 0 -10px 18px -10px var(--cl-09-light);
}

.cl-09__btn:focus-visible {
  outline: 3px solid var(--cl-09-ink);
  outline-offset: 4px;
}

.cl-09__btn--primary {
  padding: 0 34px;
  font-size: 16px;
  background: var(--cl-09-accent);
  box-shadow: 0 22px 36px -12px var(--cl-09-shade),inset 0 -16px 24px -10px var(--cl-09-shade),inset 0 18px 24px -8px var(--cl-09-light);
}

.cl-09__btn--pill {
  border-radius: 99px;
}

.cl-09__btn--icon {
  width: 58px;
  padding: 0;
  border-radius: 22px;
}

.cl-09__btn--danger {
  background: var(--cl-09-danger);
  color: oklch(0.26 0.07 25);
}

.cl-09__btn--off {
  background: color-mix(in oklch,var(--cl-09-bg) 88%,white);
  color: color-mix(in oklch,var(--cl-09-mut) 70%,transparent);
  cursor: not-allowed;
  opacity: .62;
  box-shadow: inset 0 6px 12px -8px var(--cl-09-shade),inset 0 -5px 10px -8px var(--cl-09-light);
}

.cl-09__btn--off:hover,
.cl-09__btn--off:active {
  translate: 0;
  --cl-09-sq: 1;
  filter: none;
  box-shadow: inset 0 6px 12px -8px var(--cl-09-shade),inset 0 -5px 10px -8px var(--cl-09-light);
}

.cl-09__chip {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11.5px;
  color: var(--cl-09-mut);
  padding: 9px 16px;
  border-radius: 99px;
  text-align: center;
  background: oklch(1 0 0/.55);
  box-shadow: inset 0 -4px 10px -8px var(--cl-09-shade),inset 0 6px 10px -6px oklch(1 0 0);
}

@media (prefers-reduced-motion: reduce) {
  .cl-09 * {
    transition-duration: .01ms !important;
  }

  .cl-09__btn:active {
    --cl-09-sq: 1;
  }
}
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 Claymorphism 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: Clay Button Press and Squish States
Source: https://codefronts.com/design-styles/css-claymorphism/clay-button-press-and-squish-states/

Five clay buttons with a real physical press: on :active the object squashes on the Y axis, the drop shadow collapses, and the inset layers invert so the surface genuinely looks depressed. Includes the animator's squash-and-stretch release — the button overshoots slightly on the way back up — plus an icon button, a pill, a destructive variant and a disabled state, all keyboard-operable.
## HTML
```html
<section class="cl-09" aria-label="Clay button press and squish states demo">
  <div class="cl-09__stage">
    <header class="cl-09__head">
      <h2 class="cl-09__h">Press it. It squishes.</h2>
      <p class="cl-09__lede">Squash on Y, sink 4px, invert both insets — then release on a springy curve. Hold a button down to feel the three changes.</p>
    </header>
    <div class="cl-09__row">
      <button class="cl-09__btn cl-09__btn--primary" type="button">Start lesson</button>
      <button class="cl-09__btn" type="button">Save draft</button>
      <button class="cl-09__btn cl-09__btn--pill" type="button">Follow</button>
      <button class="cl-09__btn cl-09__btn--icon" type="button" aria-label="Add to favourites">
        <svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true"><path d="M12 20.3l-1.4-1.3C6 14.9 3.5 12.6 3.5 9.6A4.1 4.1 0 017.6 5.5c1.5 0 2.9.7 3.8 1.9.9-1.2 2.3-1.9 3.8-1.9a4.1 4.1 0 014.1 4.1c0 3-2.5 5.3-7.1 9.4z" fill="currentColor"/></svg>
      </button>
      <button class="cl-09__btn cl-09__btn--danger" type="button">Delete</button>
      <button class="cl-09__btn cl-09__btn--off" type="button" disabled aria-disabled="true">Unavailable</button>
    </div>
    <p class="cl-09__chip" aria-hidden="true">scale 1 .93 · translate 0 4px · insets inverted · overshoot on release</p>
  </div>
</section>
```
## CSS
```css
.cl-09,
.cl-09 *,
.cl-09 *::before,
.cl-09 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cl-09 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--cl-09-bg);
  --cl-09-bg: oklch(0.94 0.03 165);
  --cl-09-clay: oklch(0.88 0.06 170);
  --cl-09-accent: oklch(0.82 0.13 155);
  --cl-09-danger: oklch(0.79 0.14 25);
  --cl-09-shade: oklch(0.55 0.09 170/.55);
  --cl-09-light: oklch(1 0.015 170/.95);
  --cl-09-ink: oklch(0.28 0.05 170);
  --cl-09-mut: oklch(0.5 0.04 170);
  font-family: system-ui,-apple-system,'Segoe UI',sans-serif;
  color: var(--cl-09-ink);
}

.cl-09__stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  align-content: center;
  gap: clamp(24px,4vw,40px);
  padding: clamp(20px,5vw,58px);
  background: radial-gradient(75% 55% at 50% 5%,oklch(0.97 0.035 165),transparent 65%);
}

.cl-09__head {
  text-align: center;
  max-width: 56ch;
  display: grid;
  gap: 9px;
}

.cl-09__h {
  font-size: clamp(24px,3.8vw,34px);
  font-weight: 780;
  letter-spacing: -.02em;
  text-wrap: balance;
}

.cl-09__lede {
  font-size: 15px;
  line-height: 1.6;
  color: var(--cl-09-mut);
  text-wrap: pretty;
}

.cl-09__row {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(12px,2vw,20px);
  justify-content: center;
  align-items: center;
  width: min(100%,880px);
}

.cl-09__btn {
  --cl-09-sq: 1;
  display: grid;
  place-items: center;
  min-height: 58px;
  padding: 0 30px;
  border: 0;
  border-radius: 26px;
  font: inherit;
  font-size: 15px;
  font-weight: 680;
  letter-spacing: -.005em;
  color: var(--cl-09-ink);
  cursor: pointer;
  background: var(--cl-09-clay);
  scale: 1 var(--cl-09-sq);
  box-shadow: 0 18px 30px -10px var(--cl-09-shade),inset 0 -14px 22px -10px var(--cl-09-shade),inset 0 16px 22px -8px var(--cl-09-light);
  transition: scale .2s cubic-bezier(.2,.8,.3,1.5),translate .2s cubic-bezier(.2,.8,.3,1.5),box-shadow .2s ease,filter .25s;
}

.cl-09__btn:hover {
  translate: 0 -3px;
  filter: brightness(1.03);
}

.cl-09__btn:active {
  --cl-09-sq: .93;
  translate: 0 4px;
  filter: none;
  box-shadow: inset 0 14px 20px -8px var(--cl-09-shade),inset 0 -10px 18px -10px var(--cl-09-light);
}

.cl-09__btn:focus-visible {
  outline: 3px solid var(--cl-09-ink);
  outline-offset: 4px;
}

.cl-09__btn--primary {
  padding: 0 34px;
  font-size: 16px;
  background: var(--cl-09-accent);
  box-shadow: 0 22px 36px -12px var(--cl-09-shade),inset 0 -16px 24px -10px var(--cl-09-shade),inset 0 18px 24px -8px var(--cl-09-light);
}

.cl-09__btn--pill {
  border-radius: 99px;
}

.cl-09__btn--icon {
  width: 58px;
  padding: 0;
  border-radius: 22px;
}

.cl-09__btn--danger {
  background: var(--cl-09-danger);
  color: oklch(0.26 0.07 25);
}

.cl-09__btn--off {
  background: color-mix(in oklch,var(--cl-09-bg) 88%,white);
  color: color-mix(in oklch,var(--cl-09-mut) 70%,transparent);
  cursor: not-allowed;
  opacity: .62;
  box-shadow: inset 0 6px 12px -8px var(--cl-09-shade),inset 0 -5px 10px -8px var(--cl-09-light);
}

.cl-09__btn--off:hover,
.cl-09__btn--off:active {
  translate: 0;
  --cl-09-sq: 1;
  filter: none;
  box-shadow: inset 0 6px 12px -8px var(--cl-09-shade),inset 0 -5px 10px -8px var(--cl-09-light);
}

.cl-09__chip {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11.5px;
  color: var(--cl-09-mut);
  padding: 9px 16px;
  border-radius: 99px;
  text-align: center;
  background: oklch(1 0 0/.55);
  box-shadow: inset 0 -4px 10px -8px var(--cl-09-shade),inset 0 6px 10px -6px oklch(1 0 0);
}

@media (prefers-reduced-motion: reduce) {
  .cl-09 * {
    transition-duration: .01ms !important;
  }

  .cl-09__btn:active {
    --cl-09-sq: 1;
  }
}
```

How this works

A convincing press changes three things at once, in one transition:

.btn{
  --sq: 1;                                  /* squash factor */
  scale: 1 var(--sq);
  box-shadow: 0 18px 30px -10px var(--shade),
              inset 0 -14px 22px -10px var(--shade),
              inset 0 16px 22px -8px  var(--light);
  transition: scale .18s cubic-bezier(.2,.8,.3,1.4), box-shadow .18s, translate .18s;
}
.btn:active{
  --sq: .93;                 /* 1. squash vertically */
  translate: 0 4px;          /* 2. sink toward the page */
  box-shadow: inset 0 14px 20px -8px var(--shade),   /* 3. insets INVERT */
              inset 0 -10px 18px -10px var(--light);
}

Point 3 is the one people miss. Removing the drop shadow is not enough — the highlight has to move to the bottom, because a depressed surface is lit from below by bounce light. Invert the two insets and the button stops looking like it shrank and starts looking pressed.

The release uses an overshooting easing curve (cubic-bezier(.2,.8,.3,1.4)) on scale only. Because the curve's final control point exceeds 1, the button springs past its resting height and settles — classic squash-and-stretch, 180 ms, no keyframes needed. Applying that curve to the shadow too would look rubbery, so the shadow uses a plain ease.

All motion is scale and translate (compositor properties) plus a shadow color/offset change. Nothing touches layout, so a whole toolbar of these stays at 60 fps.

Make it yours

  • Heavier press for primary CTAs: --sq:.9 and translate:0 6px. Lighter for dense toolbars: .97 and 2px.
  • Add a bloom: filter:brightness(1.04) on :hover makes the clay look warmed by the cursor without changing any color token.
  • Long-press affordance: a 600 ms transition-delay-free width animation on an inner bar turns any of these into a hold-to-confirm button.
  • Sound-free haptics on mobile: pair :active with a 10 ms navigator.vibrate(10) if you already ship JS — the CSS needs no change.

Gotchas — read before shipping

  • Never rely on :hover alone for the interactive read — touch devices skip it. Every state here is duplicated on :focus-visible and :active.
  • Don't animate height or padding for the squash; it reflows the whole row. scale is free and does not disturb siblings.
  • Scaling a button scales its text too — keep the factor above ~0.9 or the label visibly distorts.
  • Disabled clay must lose its shadow AND its saturation, or it reads as pressable. This demo drops to a flat inset with 55% opacity and sets aria-disabled.
  • Overshoot easing on the press-down (rather than the release) feels broken — the finger is still on the button. Apply the springy curve to the return only.

Browser support

ChromeSafariFirefoxEdge
114+17.5+121+114+

Floor set by oklch(), color-mix(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 104+.

Standalone scale/translate properties: Chrome 104 / Safari 14.1 / Firefox 72. For older Chromium use transform:scaleY() — identical result, one extra line.

Techniques used in this demo

Search CodeFronts

Loading…