51 CSS Buttons42 / 51

Pure CSSMIT licensed

CSS Aged Brass Subway Token Button

A 1953 transit token: milled rim, tarnished brass, and a knocked-out letter you can see the background through — a mask-composite cut, not a coloured glyph. Press it and the token drops into the slot and springs back up.

Published Updated

Live Demo
Try it

The code

<div class="cb-42">
  <div class="cb-42__stage">
    <button class="cb-42__btn" type="button" aria-label="Use one transit token">
      <span class="cb-42__slot" aria-hidden="true"></span>
      <span class="cb-42__disc" aria-hidden="true">
        <span class="cb-42__rim"></span>
        <span class="cb-42__patina"></span>
        <span class="cb-42__glyph">T</span>
      </span>
    </button>
  </div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Oswald:wght@500;600;700&display=swap');

.cb-42 {
  --bg: #191c1a;
  --brass: #a98a4a;
  --brass-hi: #dcbe7c;
  --brass-lo: #6d5527;
  --patina: #4f7a5f;
  --reed-hi: #cfae68;
  --reed-lo: #7a5f2c;
  --size: 7.5rem;
  --sans: "Oswald",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: radial-gradient(30rem 20rem at 50% 44%, #26302a, transparent 74%), var(--bg);
  font-family: var(--sans);
}

@supports (color: oklch(50% 0 0)) {
  .cb-42 {
    --brass: oklch(64% .08 88);
    --brass-hi: oklch(82% .09 90);
    --brass-lo: oklch(44% .07 85);
  }
}

@media (prefers-color-scheme: light) {
  .cb-42:not([data-theme="dark"]) {
    --bg: #242926;
  }
}

.cb-42[data-theme="light"] {
  --bg: #242926;
}

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

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

.cb-42__btn {
  position: relative;
  cursor: pointer;
  border: 0;
  background: transparent;
  padding: 0;
  width: var(--size);
  height: calc(var(--size) + 1.4rem);
  display: grid;
  place-items: start center;
  font: inherit;
  -webkit-tap-highlight-color: transparent;
}

.cb-42__slot {
  position: absolute;
  left: 12%;
  right: 12%;
  bottom: .35rem;
  height: .5rem;
  border-radius: 999px;
  background: linear-gradient(180deg, #0a0c0b, #1d221f);
  box-shadow: inset 0 2px 4px rgba(0,0,0,.9), 0 1px 0 rgba(255,255,255,.08);
}

.cb-42__disc {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--size);
  height: var(--size);
  border-radius: 50%;
  isolation: isolate;
  color: var(--brass-lo);
  background: radial-gradient(circle at 34% 28%, var(--brass-hi), transparent 54%), radial-gradient(circle at 72% 78%, var(--brass-lo), transparent 56%), linear-gradient(152deg, var(--brass), color-mix(in oklab,var(--brass) 80%,black));
  box-shadow: inset 0 2px 3px rgba(255,255,255,.4), inset 0 -3px 6px rgba(0,0,0,.45), 0 14px 22px -10px rgba(0,0,0,.8);
  transition: translate .3s cubic-bezier(.16,1,.3,1), scale .3s ease, filter .3s ease;
}

.cb-42__rim {
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: 50%;
  background: repeating-conic-gradient(from 0deg, var(--reed-hi) 0deg 1.6deg, var(--reed-lo) 1.6deg 3.2deg);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 6px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 6px));
  opacity: .85;
}

.cb-42__patina {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: 50%;
  mix-blend-mode: color-burn;
  opacity: .45;
  background: radial-gradient(2rem 1.6rem at 26% 74%, var(--patina), transparent 72%), radial-gradient(1.6rem 2rem at 76% 32%, var(--patina), transparent 74%);
}

.cb-42__glyph {
  position: relative;
  z-index: 3;
  font-size: 3.1rem;
  font-weight: 700;
  line-height: 1;
  color: var(--brass-lo);
  text-shadow: 0 1px 0 rgba(255,255,255,.35), 0 -1px 0 rgba(0,0,0,.45);
}

@supports ((mask-composite: exclude) or (-webkit-mask-composite: destination-out)) {
  .cb-42__glyph {
    color: transparent;
    text-shadow: none;
  }

  .cb-42__disc {
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Ctext x='60' y='86' font-family='Oswald,sans-serif' font-size='78' font-weight='700' text-anchor='middle' fill='%23000'%3ET%3C/text%3E%3C/svg%3E") 50% 50%/60% 60% no-repeat, linear-gradient(#000,#000);
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Ctext x='60' y='86' font-family='Oswald,sans-serif' font-size='78' font-weight='700' text-anchor='middle' fill='%23000'%3ET%3C/text%3E%3C/svg%3E") 50% 50%/60% 60% no-repeat, linear-gradient(#000,#000);
    -webkit-mask-composite: destination-out;
    mask-composite: exclude;
  }
}

.cb-42__btn:hover .cb-42__disc {
  translate: 0 -3px;
  filter: brightness(1.06);
}

.cb-42__btn:focus-visible {
  outline: none;
}

.cb-42__btn:focus-visible .cb-42__disc {
  outline: 2px solid var(--brass-hi);
  outline-offset: 5px;
}

.cb-42__btn:active .cb-42__disc {
  animation: cb-42-drop .62s cubic-bezier(.3,.05,.4,1);
}

@keyframes cb-42-drop {
  0% {
    translate: 0 0;
  }

  38% {
    translate: 0 1.15rem;
    scale: 1 .94;
  }

  62% {
    translate: 0 -.3rem;
    scale: 1 1.02;
  }

  100% {
    translate: 0 0;
    scale: 1 1;
  }
}

.cb-42__btn:disabled {
  cursor: not-allowed;
  filter: grayscale(.7) brightness(.85);
}

@media (prefers-reduced-motion: reduce) {
  .cb-42__disc {
    transition: none;
    animation: none !important;
  }
}

@media (forced-colors: active) {
  .cb-42__disc {
    background: ButtonFace;
    border: 1px solid ButtonText;
    box-shadow: none;
    mask: none;
    -webkit-mask: none;
  }

  .cb-42__glyph {
    color: ButtonText;
    text-shadow: none;
  }

  .cb-42__rim,
    .cb-42__patina,
    .cb-42__slot {
    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 Aged Brass Subway Token Button
Source: https://codefronts.com/components/css-buttons/css-aged-brass-subway-token-button/

A 1953 transit token: milled rim, tarnished brass, and a knocked-out letter you can see the background through — a mask-composite cut, not a coloured glyph. Press it and the token drops into the slot and springs back up.
## HTML
```html
<div class="cb-42">
  <div class="cb-42__stage">
    <button class="cb-42__btn" type="button" aria-label="Use one transit token">
      <span class="cb-42__slot" aria-hidden="true"></span>
      <span class="cb-42__disc" aria-hidden="true">
        <span class="cb-42__rim"></span>
        <span class="cb-42__patina"></span>
        <span class="cb-42__glyph">T</span>
      </span>
    </button>
  </div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Oswald:wght@500;600;700&display=swap');

.cb-42 {
  --bg: #191c1a;
  --brass: #a98a4a;
  --brass-hi: #dcbe7c;
  --brass-lo: #6d5527;
  --patina: #4f7a5f;
  --reed-hi: #cfae68;
  --reed-lo: #7a5f2c;
  --size: 7.5rem;
  --sans: "Oswald",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: radial-gradient(30rem 20rem at 50% 44%, #26302a, transparent 74%), var(--bg);
  font-family: var(--sans);
}

@supports (color: oklch(50% 0 0)) {
  .cb-42 {
    --brass: oklch(64% .08 88);
    --brass-hi: oklch(82% .09 90);
    --brass-lo: oklch(44% .07 85);
  }
}

@media (prefers-color-scheme: light) {
  .cb-42:not([data-theme="dark"]) {
    --bg: #242926;
  }
}

.cb-42[data-theme="light"] {
  --bg: #242926;
}

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

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

.cb-42__btn {
  position: relative;
  cursor: pointer;
  border: 0;
  background: transparent;
  padding: 0;
  width: var(--size);
  height: calc(var(--size) + 1.4rem);
  display: grid;
  place-items: start center;
  font: inherit;
  -webkit-tap-highlight-color: transparent;
}

.cb-42__slot {
  position: absolute;
  left: 12%;
  right: 12%;
  bottom: .35rem;
  height: .5rem;
  border-radius: 999px;
  background: linear-gradient(180deg, #0a0c0b, #1d221f);
  box-shadow: inset 0 2px 4px rgba(0,0,0,.9), 0 1px 0 rgba(255,255,255,.08);
}

.cb-42__disc {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--size);
  height: var(--size);
  border-radius: 50%;
  isolation: isolate;
  color: var(--brass-lo);
  background: radial-gradient(circle at 34% 28%, var(--brass-hi), transparent 54%), radial-gradient(circle at 72% 78%, var(--brass-lo), transparent 56%), linear-gradient(152deg, var(--brass), color-mix(in oklab,var(--brass) 80%,black));
  box-shadow: inset 0 2px 3px rgba(255,255,255,.4), inset 0 -3px 6px rgba(0,0,0,.45), 0 14px 22px -10px rgba(0,0,0,.8);
  transition: translate .3s cubic-bezier(.16,1,.3,1), scale .3s ease, filter .3s ease;
}

.cb-42__rim {
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: 50%;
  background: repeating-conic-gradient(from 0deg, var(--reed-hi) 0deg 1.6deg, var(--reed-lo) 1.6deg 3.2deg);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 6px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 6px), #000 calc(100% - 6px));
  opacity: .85;
}

.cb-42__patina {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: 50%;
  mix-blend-mode: color-burn;
  opacity: .45;
  background: radial-gradient(2rem 1.6rem at 26% 74%, var(--patina), transparent 72%), radial-gradient(1.6rem 2rem at 76% 32%, var(--patina), transparent 74%);
}

.cb-42__glyph {
  position: relative;
  z-index: 3;
  font-size: 3.1rem;
  font-weight: 700;
  line-height: 1;
  color: var(--brass-lo);
  text-shadow: 0 1px 0 rgba(255,255,255,.35), 0 -1px 0 rgba(0,0,0,.45);
}

@supports ((mask-composite: exclude) or (-webkit-mask-composite: destination-out)) {
  .cb-42__glyph {
    color: transparent;
    text-shadow: none;
  }

  .cb-42__disc {
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Ctext x='60' y='86' font-family='Oswald,sans-serif' font-size='78' font-weight='700' text-anchor='middle' fill='%23000'%3ET%3C/text%3E%3C/svg%3E") 50% 50%/60% 60% no-repeat, linear-gradient(#000,#000);
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Ctext x='60' y='86' font-family='Oswald,sans-serif' font-size='78' font-weight='700' text-anchor='middle' fill='%23000'%3ET%3C/text%3E%3C/svg%3E") 50% 50%/60% 60% no-repeat, linear-gradient(#000,#000);
    -webkit-mask-composite: destination-out;
    mask-composite: exclude;
  }
}

.cb-42__btn:hover .cb-42__disc {
  translate: 0 -3px;
  filter: brightness(1.06);
}

.cb-42__btn:focus-visible {
  outline: none;
}

.cb-42__btn:focus-visible .cb-42__disc {
  outline: 2px solid var(--brass-hi);
  outline-offset: 5px;
}

.cb-42__btn:active .cb-42__disc {
  animation: cb-42-drop .62s cubic-bezier(.3,.05,.4,1);
}

@keyframes cb-42-drop {
  0% {
    translate: 0 0;
  }

  38% {
    translate: 0 1.15rem;
    scale: 1 .94;
  }

  62% {
    translate: 0 -.3rem;
    scale: 1 1.02;
  }

  100% {
    translate: 0 0;
    scale: 1 1;
  }
}

.cb-42__btn:disabled {
  cursor: not-allowed;
  filter: grayscale(.7) brightness(.85);
}

@media (prefers-reduced-motion: reduce) {
  .cb-42__disc {
    transition: none;
    animation: none !important;
  }
}

@media (forced-colors: active) {
  .cb-42__disc {
    background: ButtonFace;
    border: 1px solid ButtonText;
    box-shadow: none;
    mask: none;
    -webkit-mask: none;
  }

  .cb-42__glyph {
    color: ButtonText;
    text-shadow: none;
  }

  .cb-42__rim,
    .cb-42__patina,
    .cb-42__slot {
    display: none;
  }
}
```

How this works

The letter is genuinely removed from the metal. A text layer masks the disc using mask-composite, so the hole shows whatever is behind the button:

.cb-42__disc{
  -webkit-mask:var(--glyph-mask), linear-gradient(#000,#000);
  -webkit-mask-composite:destination-out;
  mask-composite:exclude;
}

Here the glyph mask is an inline SVG <text> encoded in a data URI, which keeps the cut crisp at any size and needs no font loading gymnastics. The rim is a repeating-conic-gradient reeded ring, and the drop is a two-stage keyframe: fall, land, rebound — with an ease-in descent and a spring return, which is what stops it looking like a simple bounce:

@keyframes cb-42-drop{
  0%{ translate:0 0; }
  38%{ translate:0 1.1rem; scale:1 .94; }
  62%{ translate:0 -.25rem; }
  100%{ translate:0 0; }
}

Make it yours

  • Change the cut letter by editing the data-URI SVG's text content — any single glyph works.
  • Swap brass for nickel by desaturating the three metal tokens.
  • Deepen the patina by raising the blotch layer's opacity; real tokens are unevenly worn.
  • Remove the drop and keep a simple press if the button is used in a dense toolbar.

Gotchas — read before shipping

  • A knocked-out glyph is invisible to assistive tech. The button needs a real aria-label — the hole is decoration.
  • mask-composite spelling differs per engine (destination-out vs exclude); declare both, and put the base layer last for the prefixed version.
  • Data-URI SVG masks must be URL-encoded (%3C, %22) — raw angle brackets break in some parsers.
  • Because the letter is a hole, the button cannot sit on a busy background or the glyph becomes unreadable.
  • Circular buttons need their accessible name from aria-label and a hit area of at least 44px — this one is 7.5rem.

Browser support

ChromeSafariFirefoxEdge
120+15.4+53+120+

mask-composite: Chrome 120 / Safari 15.4 (prefixed earlier) / Firefox 53. The @supports block keeps a solid engraved glyph as the fallback so the token still reads correctly.

Techniques used in this demo

Search CodeFronts

Loading…