22 CSS Brutalism09 / 22

Pure CSSMIT licensed

Neubrutalist Blog Card Grid

Three article cards with black-framed cover images, a rotated pastel category chip, a heavy headline and a byline row. Hover moves the card toward its shadow rather than away from it — a small inversion of the usual lift that makes the block feel like a physical object being pressed onto the page.

Published

Live Demo
Try it

The code

<div class="brt-09">
  <input class="brt-09__sr" type="checkbox" id="brt-09-dark">
  <label class="brt-09__theme" for="brt-09-dark">
    <svg class="brt-09__moon" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M20 14.2A8.2 8.2 0 0 1 9.8 4 8.4 8.4 0 1 0 20 14.2" stroke="currentColor" stroke-width="2.2" stroke-linejoin="round"/></svg>
    <svg class="brt-09__sun" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="4.2" stroke="currentColor" stroke-width="2.2"/><path d="M12 3v2.2M12 18.8V21M3 12h2.2M18.8 12H21M5.6 5.6l1.6 1.6M16.8 16.8l1.6 1.6M18.4 5.6l-1.6 1.6M7.2 16.8l-1.6 1.6" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>
    <span class="brt-09__vh">Dark theme</span>
  </label>

  <div class="brt-09__stage">
    <section class="brt-09__wrap" aria-labelledby="brt-09-h">
      <header class="brt-09__head">
        <h2 class="brt-09__h" id="brt-09-h">Field notes</h2>
        <a class="brt-09__all" href="#brt-09-h">All 84 posts
          <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M5 12h13m0 0-5-5m5 5-5 5" stroke="currentColor" stroke-width="2.6" stroke-linecap="square"/></svg>
        </a>
      </header>

      <ul class="brt-09__grid">
        <li class="brt-09__card">
          <img class="brt-09__shot" src="https://images.unsplash.com/photo-1498050108023-c5249f4df085?w=640&amp;h=400&amp;fit=crop&amp;auto=format" width="640" height="400" alt="A code editor open on a laptop beside a notebook" loading="lazy" decoding="async">
          <span class="brt-09__chip" style="--chip:var(--butter)">Engineering</span>
          <h3 class="brt-09__t"><a href="#brt-09-h">We deleted 12,000 lines of CSS and nothing broke</a></h3>
          <p class="brt-09__p">Six months of dead selectors, four design systems, one very long afternoon with coverage tooling.</p>
          <p class="brt-09__by">
            <img class="brt-09__avatar" src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=120&amp;h=120&amp;fit=crop&amp;auto=format" width="120" height="120" alt="Jordan Lee" loading="lazy" decoding="async">
            <span>Jordan Lee</span><span aria-hidden="true">·</span><time datetime="2026-08-04">4 Aug</time>
            <span class="brt-09__read">6 min read</span>
          </p>
        </li>

        <li class="brt-09__card">
          <img class="brt-09__shot" src="https://images.unsplash.com/photo-1522202176988-66273c2fd55f?w=640&amp;h=400&amp;fit=crop&amp;auto=format" width="640" height="400" alt="Four colleagues reviewing work around a shared table" loading="lazy" decoding="async">
          <span class="brt-09__chip" style="--chip:var(--mint)">Process</span>
          <h3 class="brt-09__t"><a href="#brt-09-h">Design review, but nobody presents</a></h3>
          <p class="brt-09__p">Async critique with written stakes. Fewer meetings, sharper feedback, and a paper trail you can search.</p>
          <p class="brt-09__by">
            <img class="brt-09__avatar" src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=120&amp;h=120&amp;fit=crop&amp;auto=format" width="120" height="120" alt="Priya Sharma" loading="lazy" decoding="async">
            <span>Priya Sharma</span><span aria-hidden="true">·</span><time datetime="2026-07-28">28 Jul</time>
            <span class="brt-09__read">9 min read</span>
          </p>
        </li>

        <li class="brt-09__card">
          <img class="brt-09__shot" src="https://images.unsplash.com/photo-1454165804606-c3d57bc86b40?w=640&amp;h=400&amp;fit=crop&amp;auto=format" width="640" height="400" alt="Printed charts and a laptop showing analytics on a desk" loading="lazy" decoding="async">
          <span class="brt-09__chip" style="--chip:var(--sky)">Metrics</span>
          <h3 class="brt-09__t"><a href="#brt-09-h">The only three numbers on our wall</a></h3>
          <p class="brt-09__p">Why we retired 41 dashboards and kept activation, retained revenue and time-to-first-deploy.</p>
          <p class="brt-09__by">
            <img class="brt-09__avatar" src="https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=120&amp;h=120&amp;fit=crop&amp;auto=format" width="120" height="120" alt="Sam Rivera" loading="lazy" decoding="async">
            <span>Sam Rivera</span><span aria-hidden="true">·</span><time datetime="2026-07-19">19 Jul</time>
            <span class="brt-09__read">4 min read</span>
          </p>
        </li>
      </ul>
    </section>
  </div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&display=swap');

.brt-09 {
  --page: #f7f4ec;
  --panel: #ffffff;
  --ink: #0b0b0f;
  --on: #0b0b0f;
  --muted: #4a4a52;
  --lilac: #c4b5fd;
  --mint: #86efac;
  --coral: #fda4af;
  --butter: #fde68a;
  --sky: #93c5fd;
  --edge: #000000;
  --sh: #000000;
  --bw: 3px;
  --r: 14px;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  position: relative;
  box-sizing: border-box;
  background: var(--page);
  color: var(--ink);
  font-family: 'Space Grotesk','Sora',ui-sans-serif,system-ui,sans-serif;
}

.brt-09 *,
.brt-09 *::before,
.brt-09 *::after {
  box-sizing: border-box;
}

@supports (color: oklch(0.7 0.1 300)) {
  .brt-09 {
    --page: oklch(0.968 0.016 88);
    --panel: oklch(1 0 0);
    --ink: oklch(0.16 0.01 285);
    --lilac: oklch(0.82 0.11 296);
    --mint: oklch(0.89 0.13 155);
    --coral: oklch(0.83 0.10 15);
    --butter: oklch(0.92 0.11 95);
    --sky: oklch(0.83 0.09 250);
  }
}

.brt-09__vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.brt-09__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.brt-09__theme {
  position: absolute;
  inset-block-start: 1rem;
  inset-inline-end: 1rem;
  z-index: 5;
  inline-size: 2.5rem;
  block-size: 2.5rem;
  display: grid;
  place-items: center;
  background: var(--panel);
  color: var(--ink);
  border: var(--bw) solid var(--edge);
  border-radius: 10px;
  box-shadow: 4px 4px 0 var(--sh);
  cursor: pointer;
  transition: translate .14s ease, box-shadow .14s ease;
}

.brt-09__theme::after {
  content: '';
  position: absolute;
  inset: -2px;
}

.brt-09__theme svg {
  inline-size: 1.25rem;
  block-size: 1.25rem;
}

.brt-09__theme:active {
  translate: 2px 2px;
  box-shadow: 2px 2px 0 var(--sh);
}

.brt-09__sr:focus-visible + .brt-09__theme {
  outline: 3px solid var(--edge);
  outline-offset: 3px;
}

.brt-09__sun {
  display: none;
}

.brt-09__stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(1.5rem,4.5vw,3.5rem);
}

.brt-09__wrap {
  inline-size: min(74rem,100%);
}

.brt-09__head {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: baseline;
  justify-content: space-between;
  margin-block-end: clamp(1.5rem,3.5vw,2.25rem);
  padding-inline-end: 3rem;
}

.brt-09__h {
  margin: 0;
  font-size: clamp(1.9rem,5vw,2.9rem);
  font-weight: 700;
  letter-spacing: -.03em;
}

.brt-09__all {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  min-block-size: 2.75rem;
  color: var(--ink);
  font-weight: 700;
  font-size: .95rem;
  text-decoration: 3px underline var(--lilac);
  text-underline-offset: 5px;
}

.brt-09__all svg {
  inline-size: 1.15rem;
  block-size: 1.15rem;
}

.brt-09__all:hover {
  background: var(--lilac);
  color: var(--on);
}

.brt-09__all:focus-visible {
  outline: 3px solid var(--edge);
  outline-offset: 3px;
}

.brt-09__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: clamp(1.25rem,2.6vw,1.9rem);
  grid-template-columns: repeat(auto-fit,minmax(min(17rem,100%),1fr));
}

.brt-09__card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  color: var(--ink);
  border: var(--bw) solid var(--edge);
  border-radius: var(--r);
  box-shadow: 8px 8px 0 var(--sh);
  transition: translate .15s ease, box-shadow .15s ease;
}

.brt-09__card:hover,
.brt-09__card:focus-within {
  translate: 3px 3px;
  box-shadow: 4px 4px 0 var(--sh);
}

.brt-09__shot {
  display: block;
  inline-size: 100%;
  aspect-ratio: 16/10;
  object-fit: cover;
  background: var(--page);
  border-block-end: var(--bw) solid var(--edge);
  border-start-start-radius: calc(var(--r) - 3px);
  border-start-end-radius: calc(var(--r) - 3px);
}

.brt-09__chip {
  align-self: flex-start;
  margin: -1.1rem 0 0 1.1rem;
  padding: .35rem .7rem;
  rotate: -2.5deg;
  background: var(--chip,var(--butter));
  color: var(--on);
  border: var(--bw) solid var(--edge);
  border-radius: 8px;
  box-shadow: 3px 3px 0 var(--sh);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
}

.brt-09__t {
  margin: .95rem 1.15rem 0;
  font-size: 1.22rem;
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1.18;
  text-wrap: balance;
}

.brt-09__t a {
  color: inherit;
  text-decoration: none;
}

.brt-09__t a::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--r);
}

.brt-09__t a:focus-visible {
  outline: none;
}

.brt-09__card:focus-within .brt-09__t a::after {
  outline: 3px solid var(--edge);
  outline-offset: 4px;
}

.brt-09__p {
  margin: .6rem 1.15rem 1.1rem;
  font-size: .94rem;
  line-height: 1.5;
  color: var(--muted);
  text-wrap: pretty;
}

.brt-09__by {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .45rem;
  margin: auto 0 0;
  padding: .85rem 1.15rem;
  border-block-start: 2px solid var(--edge);
  font-size: .82rem;
  font-weight: 600;
  color: var(--ink);
}

.brt-09__avatar {
  inline-size: 1.9rem;
  block-size: 1.9rem;
  object-fit: cover;
  aspect-ratio: 1;
  background: var(--page);
  border: 2px solid var(--edge);
  border-radius: 50%;
}

.brt-09__read {
  margin-inline-start: auto;
  padding: .25rem .5rem;
  background: var(--page);
  border: 2px solid var(--edge);
  border-radius: 7px;
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .04em;
  white-space: nowrap;
}

.brt-09:has(#brt-09-dark:checked) {
  --page: #141319;
  --panel: #211f29;
  --ink: #f7f5ef;
  --muted: #b5b1c2;
  --edge: #f7f5ef;
  --sh: #f7f5ef;
}

.brt-09:has(#brt-09-dark:checked) .brt-09__moon {
  display: none;
}

.brt-09:has(#brt-09-dark:checked) .brt-09__sun {
  display: block;
}

@media (prefers-color-scheme: dark) {
  .brt-09:not(:has(#brt-09-dark:checked)) {
    --page: #141319;
    --panel: #211f29;
    --ink: #f7f5ef;
    --muted: #b5b1c2;
    --edge: #f7f5ef;
    --sh: #f7f5ef;
  }

  .brt-09:not(:has(#brt-09-dark:checked)) .brt-09__moon {
    display: none;
  }

  .brt-09:not(:has(#brt-09-dark:checked)) .brt-09__sun {
    display: block;
  }
}

@media (prefers-reduced-motion: reduce) {
  .brt-09 *,
    .brt-09 *::before,
    .brt-09 *::after {
    transition: none !important;
    animation: none !important;
  }
}

@media (forced-colors: active) {
  .brt-09__card,
    .brt-09__chip,
    .brt-09__read,
    .brt-09__theme {
    border-color: CanvasText;
    box-shadow: none;
  }
}
/* stage children default to min-width:auto, so a wide heading or grid
   track forces the panel past the stage; 0 lets it shrink instead */

.brt-09__stage > * {
  min-inline-size: 0;
  max-inline-size: 100%;
}
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 Brutalism 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: Neubrutalist Blog Card Grid
Source: https://codefronts.com/design-styles/css-brutalist/neubrutalist-blog-card-grid/

Three article cards with black-framed cover images, a rotated pastel category chip, a heavy headline and a byline row. Hover moves the card toward its shadow rather than away from it — a small inversion of the usual lift that makes the block feel like a physical object being pressed onto the page.
## HTML
```html
<div class="brt-09">
  <input class="brt-09__sr" type="checkbox" id="brt-09-dark">
  <label class="brt-09__theme" for="brt-09-dark">
    <svg class="brt-09__moon" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M20 14.2A8.2 8.2 0 0 1 9.8 4 8.4 8.4 0 1 0 20 14.2" stroke="currentColor" stroke-width="2.2" stroke-linejoin="round"/></svg>
    <svg class="brt-09__sun" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="4.2" stroke="currentColor" stroke-width="2.2"/><path d="M12 3v2.2M12 18.8V21M3 12h2.2M18.8 12H21M5.6 5.6l1.6 1.6M16.8 16.8l1.6 1.6M18.4 5.6l-1.6 1.6M7.2 16.8l-1.6 1.6" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>
    <span class="brt-09__vh">Dark theme</span>
  </label>

  <div class="brt-09__stage">
    <section class="brt-09__wrap" aria-labelledby="brt-09-h">
      <header class="brt-09__head">
        <h2 class="brt-09__h" id="brt-09-h">Field notes</h2>
        <a class="brt-09__all" href="#brt-09-h">All 84 posts
          <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M5 12h13m0 0-5-5m5 5-5 5" stroke="currentColor" stroke-width="2.6" stroke-linecap="square"/></svg>
        </a>
      </header>

      <ul class="brt-09__grid">
        <li class="brt-09__card">
          <img class="brt-09__shot" src="https://images.unsplash.com/photo-1498050108023-c5249f4df085?w=640&amp;h=400&amp;fit=crop&amp;auto=format" width="640" height="400" alt="A code editor open on a laptop beside a notebook" loading="lazy" decoding="async">
          <span class="brt-09__chip" style="--chip:var(--butter)">Engineering</span>
          <h3 class="brt-09__t"><a href="#brt-09-h">We deleted 12,000 lines of CSS and nothing broke</a></h3>
          <p class="brt-09__p">Six months of dead selectors, four design systems, one very long afternoon with coverage tooling.</p>
          <p class="brt-09__by">
            <img class="brt-09__avatar" src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=120&amp;h=120&amp;fit=crop&amp;auto=format" width="120" height="120" alt="Jordan Lee" loading="lazy" decoding="async">
            <span>Jordan Lee</span><span aria-hidden="true">·</span><time datetime="2026-08-04">4 Aug</time>
            <span class="brt-09__read">6 min read</span>
          </p>
        </li>

        <li class="brt-09__card">
          <img class="brt-09__shot" src="https://images.unsplash.com/photo-1522202176988-66273c2fd55f?w=640&amp;h=400&amp;fit=crop&amp;auto=format" width="640" height="400" alt="Four colleagues reviewing work around a shared table" loading="lazy" decoding="async">
          <span class="brt-09__chip" style="--chip:var(--mint)">Process</span>
          <h3 class="brt-09__t"><a href="#brt-09-h">Design review, but nobody presents</a></h3>
          <p class="brt-09__p">Async critique with written stakes. Fewer meetings, sharper feedback, and a paper trail you can search.</p>
          <p class="brt-09__by">
            <img class="brt-09__avatar" src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=120&amp;h=120&amp;fit=crop&amp;auto=format" width="120" height="120" alt="Priya Sharma" loading="lazy" decoding="async">
            <span>Priya Sharma</span><span aria-hidden="true">·</span><time datetime="2026-07-28">28 Jul</time>
            <span class="brt-09__read">9 min read</span>
          </p>
        </li>

        <li class="brt-09__card">
          <img class="brt-09__shot" src="https://images.unsplash.com/photo-1454165804606-c3d57bc86b40?w=640&amp;h=400&amp;fit=crop&amp;auto=format" width="640" height="400" alt="Printed charts and a laptop showing analytics on a desk" loading="lazy" decoding="async">
          <span class="brt-09__chip" style="--chip:var(--sky)">Metrics</span>
          <h3 class="brt-09__t"><a href="#brt-09-h">The only three numbers on our wall</a></h3>
          <p class="brt-09__p">Why we retired 41 dashboards and kept activation, retained revenue and time-to-first-deploy.</p>
          <p class="brt-09__by">
            <img class="brt-09__avatar" src="https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=120&amp;h=120&amp;fit=crop&amp;auto=format" width="120" height="120" alt="Sam Rivera" loading="lazy" decoding="async">
            <span>Sam Rivera</span><span aria-hidden="true">·</span><time datetime="2026-07-19">19 Jul</time>
            <span class="brt-09__read">4 min read</span>
          </p>
        </li>
      </ul>
    </section>
  </div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&display=swap');

.brt-09 {
  --page: #f7f4ec;
  --panel: #ffffff;
  --ink: #0b0b0f;
  --on: #0b0b0f;
  --muted: #4a4a52;
  --lilac: #c4b5fd;
  --mint: #86efac;
  --coral: #fda4af;
  --butter: #fde68a;
  --sky: #93c5fd;
  --edge: #000000;
  --sh: #000000;
  --bw: 3px;
  --r: 14px;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  position: relative;
  box-sizing: border-box;
  background: var(--page);
  color: var(--ink);
  font-family: 'Space Grotesk','Sora',ui-sans-serif,system-ui,sans-serif;
}

.brt-09 *,
.brt-09 *::before,
.brt-09 *::after {
  box-sizing: border-box;
}

@supports (color: oklch(0.7 0.1 300)) {
  .brt-09 {
    --page: oklch(0.968 0.016 88);
    --panel: oklch(1 0 0);
    --ink: oklch(0.16 0.01 285);
    --lilac: oklch(0.82 0.11 296);
    --mint: oklch(0.89 0.13 155);
    --coral: oklch(0.83 0.10 15);
    --butter: oklch(0.92 0.11 95);
    --sky: oklch(0.83 0.09 250);
  }
}

.brt-09__vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.brt-09__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.brt-09__theme {
  position: absolute;
  inset-block-start: 1rem;
  inset-inline-end: 1rem;
  z-index: 5;
  inline-size: 2.5rem;
  block-size: 2.5rem;
  display: grid;
  place-items: center;
  background: var(--panel);
  color: var(--ink);
  border: var(--bw) solid var(--edge);
  border-radius: 10px;
  box-shadow: 4px 4px 0 var(--sh);
  cursor: pointer;
  transition: translate .14s ease, box-shadow .14s ease;
}

.brt-09__theme::after {
  content: '';
  position: absolute;
  inset: -2px;
}

.brt-09__theme svg {
  inline-size: 1.25rem;
  block-size: 1.25rem;
}

.brt-09__theme:active {
  translate: 2px 2px;
  box-shadow: 2px 2px 0 var(--sh);
}

.brt-09__sr:focus-visible + .brt-09__theme {
  outline: 3px solid var(--edge);
  outline-offset: 3px;
}

.brt-09__sun {
  display: none;
}

.brt-09__stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(1.5rem,4.5vw,3.5rem);
}

.brt-09__wrap {
  inline-size: min(74rem,100%);
}

.brt-09__head {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: baseline;
  justify-content: space-between;
  margin-block-end: clamp(1.5rem,3.5vw,2.25rem);
  padding-inline-end: 3rem;
}

.brt-09__h {
  margin: 0;
  font-size: clamp(1.9rem,5vw,2.9rem);
  font-weight: 700;
  letter-spacing: -.03em;
}

.brt-09__all {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  min-block-size: 2.75rem;
  color: var(--ink);
  font-weight: 700;
  font-size: .95rem;
  text-decoration: 3px underline var(--lilac);
  text-underline-offset: 5px;
}

.brt-09__all svg {
  inline-size: 1.15rem;
  block-size: 1.15rem;
}

.brt-09__all:hover {
  background: var(--lilac);
  color: var(--on);
}

.brt-09__all:focus-visible {
  outline: 3px solid var(--edge);
  outline-offset: 3px;
}

.brt-09__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: clamp(1.25rem,2.6vw,1.9rem);
  grid-template-columns: repeat(auto-fit,minmax(min(17rem,100%),1fr));
}

.brt-09__card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  color: var(--ink);
  border: var(--bw) solid var(--edge);
  border-radius: var(--r);
  box-shadow: 8px 8px 0 var(--sh);
  transition: translate .15s ease, box-shadow .15s ease;
}

.brt-09__card:hover,
.brt-09__card:focus-within {
  translate: 3px 3px;
  box-shadow: 4px 4px 0 var(--sh);
}

.brt-09__shot {
  display: block;
  inline-size: 100%;
  aspect-ratio: 16/10;
  object-fit: cover;
  background: var(--page);
  border-block-end: var(--bw) solid var(--edge);
  border-start-start-radius: calc(var(--r) - 3px);
  border-start-end-radius: calc(var(--r) - 3px);
}

.brt-09__chip {
  align-self: flex-start;
  margin: -1.1rem 0 0 1.1rem;
  padding: .35rem .7rem;
  rotate: -2.5deg;
  background: var(--chip,var(--butter));
  color: var(--on);
  border: var(--bw) solid var(--edge);
  border-radius: 8px;
  box-shadow: 3px 3px 0 var(--sh);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
}

.brt-09__t {
  margin: .95rem 1.15rem 0;
  font-size: 1.22rem;
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1.18;
  text-wrap: balance;
}

.brt-09__t a {
  color: inherit;
  text-decoration: none;
}

.brt-09__t a::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--r);
}

.brt-09__t a:focus-visible {
  outline: none;
}

.brt-09__card:focus-within .brt-09__t a::after {
  outline: 3px solid var(--edge);
  outline-offset: 4px;
}

.brt-09__p {
  margin: .6rem 1.15rem 1.1rem;
  font-size: .94rem;
  line-height: 1.5;
  color: var(--muted);
  text-wrap: pretty;
}

.brt-09__by {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .45rem;
  margin: auto 0 0;
  padding: .85rem 1.15rem;
  border-block-start: 2px solid var(--edge);
  font-size: .82rem;
  font-weight: 600;
  color: var(--ink);
}

.brt-09__avatar {
  inline-size: 1.9rem;
  block-size: 1.9rem;
  object-fit: cover;
  aspect-ratio: 1;
  background: var(--page);
  border: 2px solid var(--edge);
  border-radius: 50%;
}

.brt-09__read {
  margin-inline-start: auto;
  padding: .25rem .5rem;
  background: var(--page);
  border: 2px solid var(--edge);
  border-radius: 7px;
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .04em;
  white-space: nowrap;
}

.brt-09:has(#brt-09-dark:checked) {
  --page: #141319;
  --panel: #211f29;
  --ink: #f7f5ef;
  --muted: #b5b1c2;
  --edge: #f7f5ef;
  --sh: #f7f5ef;
}

.brt-09:has(#brt-09-dark:checked) .brt-09__moon {
  display: none;
}

.brt-09:has(#brt-09-dark:checked) .brt-09__sun {
  display: block;
}

@media (prefers-color-scheme: dark) {
  .brt-09:not(:has(#brt-09-dark:checked)) {
    --page: #141319;
    --panel: #211f29;
    --ink: #f7f5ef;
    --muted: #b5b1c2;
    --edge: #f7f5ef;
    --sh: #f7f5ef;
  }

  .brt-09:not(:has(#brt-09-dark:checked)) .brt-09__moon {
    display: none;
  }

  .brt-09:not(:has(#brt-09-dark:checked)) .brt-09__sun {
    display: block;
  }
}

@media (prefers-reduced-motion: reduce) {
  .brt-09 *,
    .brt-09 *::before,
    .brt-09 *::after {
    transition: none !important;
    animation: none !important;
  }
}

@media (forced-colors: active) {
  .brt-09__card,
    .brt-09__chip,
    .brt-09__read,
    .brt-09__theme {
    border-color: CanvasText;
    box-shadow: none;
  }
}
/* stage children default to min-width:auto, so a wide heading or grid
   track forces the panel past the stage; 0 lets it shrink instead */

.brt-09__stage > * {
  min-inline-size: 0;
  max-inline-size: 100%;
}
```

How this works

The inverted lift. Most card grids raise the card and grow a blurred shadow. Here the card slides into its own hard shadow, which reads as a press — and it is two properties, both compositor-friendly:

.brt-09__card{ box-shadow:8px 8px 0 var(--sh); transition:translate .15s ease, box-shadow .15s ease; }
.brt-09__card:hover,
.brt-09__card:focus-within{ translate:3px 3px; box-shadow:4px 4px 0 var(--sh); }

:focus-within is what makes it keyboard-equivalent: tab to the headline link and the card reacts identically.

Stretched link, honest text. Wrapping the whole card in an anchor gives screen readers a link whose name is every word in the card. Instead, link the headline and stretch it:

.brt-09__t a::after{ content:''; position:absolute; inset:0; }   /* card-sized hit area */

Anything that must stay independently clickable (a tag, an author link) needs position:relative and a higher z-index to sit above that overlay.

Images that never shift the layout. The frame declares its ratio, so the grid's final geometry is known before a single byte of image data arrives:

.brt-09__shot{ aspect-ratio:16/10; object-fit:cover; inline-size:100%; display:block;
  border-block-end:var(--bw) solid var(--edge); background:var(--page); }

Make it yours

  • Change the cover ratio in one place (aspect-ratio) — 16/10 for editorial, 1/1 for a denser three-up, 21/9 for a featured row.
  • Recolour the chip per category by setting --chip on the chip element; the border, radius and rotation stay shared.
  • Swap the hover direction by inverting the translate signs if you prefer a lift; keep the shadow change proportional either way.
  • Add a fourth card and nothing breaks — the grid is repeat(auto-fit, minmax(min(17rem,100%),1fr)).
  • For a featured lead card, give the first item grid-column: span 2 above 60rem and raise its headline size.

Gotchas — read before shipping

  • A stretched-link ::after covers everything below it in the stacking order. Any nested link needs position:relative; z-index:1 or it becomes unclickable.
  • Don't put overflow:hidden on a card that has a rotated chip overlapping its edge — the chip gets clipped. Round the image itself instead.
  • Hotlinked photos need explicit width/height attributes as well as aspect-ratio, so a failed request leaves a correctly-sized block rather than a collapsed one.
  • Reading time is content, not decoration: put it in text, not in a ::before string, or it is invisible to search engines and screen readers.
  • Avoid transitioning the card's border-width on hover — it reflows the whole grid. Move and reshape the shadow instead.
  • If the headline can wrap to three lines, set text-wrap: balance and a min-block-size on the title so card footers stay aligned.

Browser support

ChromeSafariFirefoxEdge
114+17.5+121+114+

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

aspect-ratio (Chrome 88 / Safari 15 / Firefox 89) and :focus-within (2017) are the only requirements. :has() drives the CSS-only theme toggle (Chrome 105 / Safari 15.4 / Firefox 121); without it the demo still respects prefers-color-scheme.

Techniques used in this demo

Search CodeFronts

Loading…