51 CSS Buttons30 / 51

Pure CSSMIT licensed

CSS Brutalist Concrete Slab Button

Board-formed concrete: horizontal shutter lines from the timber mould, aggregate speckle scattered through the mix, chipped corners cut by clip-path, and a press that drops the slab into its own shadow with a puff of dust at the base.

Published Updated

Live Demo
Try it

The code

<div class="cb-30">
  <div class="cb-30__stage">
    <button class="cb-30__btn" type="button">
      <span class="cb-30__dust" aria-hidden="true"></span>
      <span class="cb-30__label">Pour it</span>
    </button>
  </div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&family=Archivo:wght@600;700&display=swap');

.cb-30 {
  --bg: #c9c6c0;
  --grey: #8f8d88;
  --grey-hi: #a8a6a1;
  --grey-lo: #5d5b57;
  --fleck: #e6e3dc;
  --fleck-2: #54524e;
  --ink: #141412;
  --sans: "Archivo Black",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: repeating-linear-gradient(0deg, rgba(0,0,0,.035) 0 1px, transparent 1px 22px), var(--bg);
  font-family: var(--sans);
}

@supports (color: oklch(50% 0 0)) {
  .cb-30 {
    --bg: oklch(82% .004 85);
    --grey: oklch(62% .004 85);
    --grey-hi: oklch(70% .004 85);
    --grey-lo: oklch(44% .004 85);
  }
}

@media (prefers-color-scheme: dark) {
  .cb-30:not([data-theme="light"]) {
    --bg: #2e2d2b;
    --grey: #514f4c;
    --grey-hi: #615f5b;
    --grey-lo: #2f2e2c;
    --fleck: #8c8983;
    --fleck-2: #26251f;
    --ink: #f2f0eb;
  }
}

.cb-30[data-theme="dark"] {
  --bg: #2e2d2b;
  --grey: #514f4c;
  --grey-hi: #615f5b;
  --grey-lo: #2f2e2c;
  --fleck: #8c8983;
  --fleck-2: #26251f;
  --ink: #f2f0eb;
}

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

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

.cb-30__btn {
  position: relative;
  cursor: pointer;
  display: grid;
  place-items: center;
  min-height: 4.5rem;
  padding: 0 3rem;
  border: 0;
  clip-path: polygon(2.5% 0,100% 0,100% 88%,97% 100%,0 100%,0 9%);
  font: inherit;
  font-size: 1.25rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink);
  background-color: var(--grey);
  background-image: radial-gradient(.055rem .055rem at 18% 32%, color-mix(in oklab,var(--fleck) 55%,transparent) 99%, transparent), radial-gradient(.045rem .045rem at 62% 71%, color-mix(in oklab,var(--fleck-2) 45%,transparent) 99%, transparent), radial-gradient(.04rem .04rem at 84% 18%, color-mix(in oklab,var(--fleck) 40%,transparent) 99%, transparent), repeating-linear-gradient(0deg, rgba(0,0,0,.07) 0 1px, transparent 1px 14px), linear-gradient(168deg, var(--grey-hi), var(--grey) 55%, var(--grey-lo));
  background-size: 13px 13px, 19px 19px, 29px 29px, 100% 100%, 100% 100%;
  text-shadow: 0 1px 0 rgba(255,255,255,.28), 0 -1px 0 rgba(0,0,0,.3);
  box-shadow: 0 8px 0 0 var(--grey-lo), 0 16px 22px -8px rgba(0,0,0,.5);
  transition: translate .1s linear, box-shadow .1s linear, filter .25s ease;
}

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

.cb-30__btn:hover {
  filter: brightness(1.05);
}

.cb-30__btn:focus-visible {
  outline: 4px solid var(--ink);
  outline-offset: 6px;
}

.cb-30__btn:active {
  translate: 0 8px;
  box-shadow: 0 0 0 0 var(--grey-lo), 0 4px 8px -4px rgba(0,0,0,.5);
}

.cb-30__dust {
  position: absolute;
  left: 50%;
  bottom: -.35rem;
  width: 9rem;
  height: 1.1rem;
  margin-left: -4.5rem;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(closest-side, rgba(255,255,255,.85), transparent 72%);
  filter: blur(4px);
}

.cb-30__btn:active .cb-30__dust {
  animation: cb-30-dust .55s ease-out;
}

@keyframes cb-30-dust {
  0% {
    opacity: .85;
    scale: .4 .5;
  }

  100% {
    opacity: 0;
    scale: 1.5 1.3;
  }
}

.cb-30__btn:disabled {
  cursor: not-allowed;
  filter: grayscale(.5) brightness(.85);
  box-shadow: 0 4px 0 0 var(--grey-lo);
}

@media (prefers-reduced-motion: reduce) {
  .cb-30__btn {
    transition: none;
  }

  .cb-30__dust {
    display: none;
  }
}

@media (forced-colors: active) {
  .cb-30__btn {
    background: ButtonFace;
    color: ButtonText;
    border: 2px solid ButtonText;
    box-shadow: none;
    text-shadow: none;
    clip-path: none;
  }

  .cb-30__dust {
    display: none;
  }
}
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 Brutalist Concrete Slab Button
Source: https://codefronts.com/components/css-buttons/css-brutalist-concrete-slab-button/

Board-formed concrete: horizontal shutter lines from the timber mould, aggregate speckle scattered through the mix, chipped corners cut by clip-path, and a press that drops the slab into its own shadow with a puff of dust at the base.
## HTML
```html
<div class="cb-30">
  <div class="cb-30__stage">
    <button class="cb-30__btn" type="button">
      <span class="cb-30__dust" aria-hidden="true"></span>
      <span class="cb-30__label">Pour it</span>
    </button>
  </div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&family=Archivo:wght@600;700&display=swap');

.cb-30 {
  --bg: #c9c6c0;
  --grey: #8f8d88;
  --grey-hi: #a8a6a1;
  --grey-lo: #5d5b57;
  --fleck: #e6e3dc;
  --fleck-2: #54524e;
  --ink: #141412;
  --sans: "Archivo Black",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: repeating-linear-gradient(0deg, rgba(0,0,0,.035) 0 1px, transparent 1px 22px), var(--bg);
  font-family: var(--sans);
}

@supports (color: oklch(50% 0 0)) {
  .cb-30 {
    --bg: oklch(82% .004 85);
    --grey: oklch(62% .004 85);
    --grey-hi: oklch(70% .004 85);
    --grey-lo: oklch(44% .004 85);
  }
}

@media (prefers-color-scheme: dark) {
  .cb-30:not([data-theme="light"]) {
    --bg: #2e2d2b;
    --grey: #514f4c;
    --grey-hi: #615f5b;
    --grey-lo: #2f2e2c;
    --fleck: #8c8983;
    --fleck-2: #26251f;
    --ink: #f2f0eb;
  }
}

.cb-30[data-theme="dark"] {
  --bg: #2e2d2b;
  --grey: #514f4c;
  --grey-hi: #615f5b;
  --grey-lo: #2f2e2c;
  --fleck: #8c8983;
  --fleck-2: #26251f;
  --ink: #f2f0eb;
}

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

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

.cb-30__btn {
  position: relative;
  cursor: pointer;
  display: grid;
  place-items: center;
  min-height: 4.5rem;
  padding: 0 3rem;
  border: 0;
  clip-path: polygon(2.5% 0,100% 0,100% 88%,97% 100%,0 100%,0 9%);
  font: inherit;
  font-size: 1.25rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink);
  background-color: var(--grey);
  background-image: radial-gradient(.055rem .055rem at 18% 32%, color-mix(in oklab,var(--fleck) 55%,transparent) 99%, transparent), radial-gradient(.045rem .045rem at 62% 71%, color-mix(in oklab,var(--fleck-2) 45%,transparent) 99%, transparent), radial-gradient(.04rem .04rem at 84% 18%, color-mix(in oklab,var(--fleck) 40%,transparent) 99%, transparent), repeating-linear-gradient(0deg, rgba(0,0,0,.07) 0 1px, transparent 1px 14px), linear-gradient(168deg, var(--grey-hi), var(--grey) 55%, var(--grey-lo));
  background-size: 13px 13px, 19px 19px, 29px 29px, 100% 100%, 100% 100%;
  text-shadow: 0 1px 0 rgba(255,255,255,.28), 0 -1px 0 rgba(0,0,0,.3);
  box-shadow: 0 8px 0 0 var(--grey-lo), 0 16px 22px -8px rgba(0,0,0,.5);
  transition: translate .1s linear, box-shadow .1s linear, filter .25s ease;
}

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

.cb-30__btn:hover {
  filter: brightness(1.05);
}

.cb-30__btn:focus-visible {
  outline: 4px solid var(--ink);
  outline-offset: 6px;
}

.cb-30__btn:active {
  translate: 0 8px;
  box-shadow: 0 0 0 0 var(--grey-lo), 0 4px 8px -4px rgba(0,0,0,.5);
}

.cb-30__dust {
  position: absolute;
  left: 50%;
  bottom: -.35rem;
  width: 9rem;
  height: 1.1rem;
  margin-left: -4.5rem;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(closest-side, rgba(255,255,255,.85), transparent 72%);
  filter: blur(4px);
}

.cb-30__btn:active .cb-30__dust {
  animation: cb-30-dust .55s ease-out;
}

@keyframes cb-30-dust {
  0% {
    opacity: .85;
    scale: .4 .5;
  }

  100% {
    opacity: 0;
    scale: 1.5 1.3;
  }
}

.cb-30__btn:disabled {
  cursor: not-allowed;
  filter: grayscale(.5) brightness(.85);
  box-shadow: 0 4px 0 0 var(--grey-lo);
}

@media (prefers-reduced-motion: reduce) {
  .cb-30__btn {
    transition: none;
  }

  .cb-30__dust {
    display: none;
  }
}

@media (forced-colors: active) {
  .cb-30__btn {
    background: ButtonFace;
    color: ButtonText;
    border: 2px solid ButtonText;
    box-shadow: none;
    text-shadow: none;
    clip-path: none;
  }

  .cb-30__dust {
    display: none;
  }
}
```

How this works

Concrete is speckle plus formwork. The aggregate is a set of tiny radial gradients at different tile sizes; the shutter lines are one wide repeating gradient with a soft edge:

background-image:
  radial-gradient(.1rem .1rem at 18% 32%, var(--fleck) 99%, transparent),
  radial-gradient(.07rem .07rem at 62% 71%, var(--fleck-2) 99%, transparent),
  repeating-linear-gradient(0deg,
    rgba(0,0,0,.07) 0 1px, transparent 1px 14px),
  linear-gradient(168deg, var(--grey-hi), var(--grey), var(--grey-lo));
background-size:11px 11px, 17px 17px, 100% 100%, 100% 100%;

The chipped corners are a five-point clip-path — cast concrete never has clean 90° edges — and the press uses a hard shadow that collapses to zero while the slab travels the same distance, so it lands with no bounce:

.cb-30__btn{ box-shadow:0 8px 0 0 var(--grey-lo), 0 14px 20px -6px rgba(0,0,0,.5); }
.cb-30__btn:active{ translate:0 8px; box-shadow:0 0 0 0 var(--grey-lo); }

The dust is a blurred ellipse at the base that scales and fades on :active — one element, one keyframe.

Make it yours

  • Shift the mix with --fleck: white for limestone, dark for basalt, warm for exposed brick aggregate.
  • Change the shutter rhythm via the 14px stop — wider boards read as a larger structure.
  • Adjust the chip depth in the clip-path percentages; keep them under 4% or it looks broken rather than cast.
  • Set --grey to a saturated colour for painted concrete — the texture survives any hue.

Gotchas — read before shipping

  • Heavy uppercase type on textured backgrounds needs a real contrast check: the speckle raises the effective background lightness by several percent.
  • clip-path removes the corners and the box-shadow at those corners; add a matching filter:drop-shadow if you need a shadow that follows the chips.
  • Big hard shadows plus a translate must use identical values, or the slab appears to slide instead of sink.
  • Speckle layers must not be animated; the repaint cost across the whole slab is high.
  • In dark mode concrete goes muddy fast — lower the fleck opacity rather than lightening the base.

Browser support

ChromeSafariFirefoxEdge
111+16.2+113+111+

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

clip-path, repeating gradients and stacked box-shadows are baseline; individual transform properties need Chrome 104 / Safari 14.1 / Firefox 72.

Techniques used in this demo

Search CodeFronts

Loading…