Pure CSSMIT licensed

Elevation Without Shadows or Gradients

Three elevation levels rendered with surface tone, spacing and a single hairline — no shadow stack, no gradients. It also states the limit honestly: without shadows a floating element loses its depth cue, so the overlay here still ships a scrim, and the demo explains why that is not a contradiction.

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="mu-12" aria-labelledby="mu-12-title">
  <h2 class="mu-12__sr" id="mu-12-title">Tonal elevation levels</h2>
  <div class="mu-12__theme">
    <input class="mu-12__theme-input" type="checkbox" id="mu-12-theme">
    <label class="mu-12__theme-label" for="mu-12-theme">
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 4a8 8 0 0 0 0 16Z" fill="currentColor"/></svg>
      <span class="mu-12__sr">Toggle dark theme</span>
    </label>
  </div>

  <div class="mu-12__stage">

    <div class="mu-12__ladder">
      <article class="mu-12__level mu-12__level--0" aria-labelledby="mu-12-l0">
        <header class="mu-12__level-head">
          <h3 class="mu-12__level-title" id="mu-12-l0">Level 0 &mdash; page</h3>
          <p class="mu-12__level-spec">base tone &middot; no edge &middot; pad 24px</p>
        </header>

        <article class="mu-12__level mu-12__level--1" aria-labelledby="mu-12-l1">
          <header class="mu-12__level-head">
            <h4 class="mu-12__level-title" id="mu-12-l1">Level 1 &mdash; panel</h4>
            <p class="mu-12__level-spec">+4% lightness &middot; top hairline &middot; pad 28px</p>
          </header>

          <article class="mu-12__level mu-12__level--2" aria-labelledby="mu-12-l2">
            <header class="mu-12__level-head">
              <h5 class="mu-12__level-title" id="mu-12-l2">Level 2 &mdash; raised</h5>
              <p class="mu-12__level-spec">+8% lightness &middot; top hairline &middot; pad 32px</p>
            </header>
            <div class="mu-12__actions">
              <button class="mu-12__btn mu-12__btn--primary" type="button">Save workspace</button>
              <button class="mu-12__btn" type="button">Discard</button>
            </div>
          </article>
        </article>
      </article>
    </div>

    <section class="mu-12__overlay-demo" aria-labelledby="mu-12-l3">
      <div class="mu-12__scrim">
        <div class="mu-12__dialog">
          <h3 class="mu-12__level-title" id="mu-12-l3">Level 3 &mdash; overlay</h3>
          <p class="mu-12__level-spec">+12% lightness &middot; top hairline &middot; scrim required</p>
          <div class="mu-12__actions">
            <button class="mu-12__btn mu-12__btn--primary" type="button">Confirm transfer</button>
            <button class="mu-12__btn" type="button">Cancel</button>
          </div>
        </div>
      </div>
    </section>
  </div>
</section>
.mu-12 {
  --mu-12-pad-step: 0.25rem;
  --mu-12-round: ui-rounded, "SF Pro Rounded", "Hiragino Maru Gothic ProN", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mu-12-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  --mu-12-e0: #1b1917;
  --mu-12-e0: oklch(22% 0.006 60);
  --mu-12-e1: #232120;
  --mu-12-e1: oklch(26% 0.006 60);
  --mu-12-e2: #2c2a28;
  --mu-12-e2: oklch(30% 0.006 60);
  --mu-12-e3: #363331;
  --mu-12-e3: oklch(34% 0.007 60);
  --mu-12-edge: #4a4643;
  --mu-12-edge: oklch(42% 0.008 60);
  --mu-12-ink: #f5f2ef;
  --mu-12-ink: oklch(96% 0.005 70);
  --mu-12-ink-2: #b3aca6;
  --mu-12-ink-2: oklch(75% 0.009 65);
  --mu-12-scrim: rgba(8, 7, 6, 0.62);
  --mu-12-acc: #8fd4c4;
  --mu-12-acc: oklch(84% 0.09 175);
  --mu-12-acc-ink: #12201d;
  --mu-12-acc-ink: oklch(19% 0.02 175);
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  position: relative;
  box-sizing: border-box;
  background: var(--mu-12-e0);
  color: var(--mu-12-ink);
  font-family: var(--mu-12-round);
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}

.mu-12 *,
.mu-12 *::before,
.mu-12 *::after {
  box-sizing: border-box;
}

@media (prefers-color-scheme: light) {
  .mu-12 {
    --mu-12-e0: #eeecea;
    --mu-12-e0: oklch(94% 0.004 70);
    --mu-12-e1: #f5f3f1;
    --mu-12-e1: oklch(96.5% 0.004 70);
    --mu-12-e2: #fbfaf9;
    --mu-12-e2: oklch(98.5% 0.003 70);
    --mu-12-e3: #ffffff;
    --mu-12-e3: oklch(100% 0 0);
    --mu-12-edge: #cfcac5;
    --mu-12-edge: oklch(84% 0.007 70);
    --mu-12-ink: #1b1917;
    --mu-12-ink: oklch(22% 0.006 60);
    --mu-12-ink-2: #57514b;
    --mu-12-ink-2: oklch(45% 0.01 62);
    --mu-12-scrim: rgba(24, 22, 20, 0.48);
    --mu-12-acc: #0d6355;
    --mu-12-acc: oklch(43% 0.09 175);
    --mu-12-acc-ink: #ffffff;
    --mu-12-acc-ink: oklch(100% 0 0);
  }
}

.mu-12[data-theme="light"],
.mu-12:has(.mu-12__theme-input:checked) {
  --mu-12-e0: #eeecea;
  --mu-12-e0: oklch(94% 0.004 70);
  --mu-12-e1: #f5f3f1;
  --mu-12-e1: oklch(96.5% 0.004 70);
  --mu-12-e2: #fbfaf9;
  --mu-12-e2: oklch(98.5% 0.003 70);
  --mu-12-e3: #ffffff;
  --mu-12-e3: oklch(100% 0 0);
  --mu-12-edge: #cfcac5;
  --mu-12-edge: oklch(84% 0.007 70);
  --mu-12-ink: #1b1917;
  --mu-12-ink: oklch(22% 0.006 60);
  --mu-12-ink-2: #57514b;
  --mu-12-ink-2: oklch(45% 0.01 62);
  --mu-12-scrim: rgba(24, 22, 20, 0.48);
  --mu-12-acc: #0d6355;
  --mu-12-acc: oklch(43% 0.09 175);
  --mu-12-acc-ink: #ffffff;
  --mu-12-acc-ink: oklch(100% 0 0);
}

@media (prefers-color-scheme: dark) {
  .mu-12:has(.mu-12__theme-input:checked) {
    --mu-12-e0: #eeecea;
    --mu-12-e0: oklch(94% 0.004 70);
    --mu-12-e1: #f5f3f1;
    --mu-12-e1: oklch(96.5% 0.004 70);
    --mu-12-e2: #fbfaf9;
    --mu-12-e2: oklch(98.5% 0.003 70);
    --mu-12-e3: #ffffff;
    --mu-12-e3: oklch(100% 0 0);
    --mu-12-edge: #cfcac5;
    --mu-12-edge: oklch(84% 0.007 70);
    --mu-12-ink: #1b1917;
    --mu-12-ink: oklch(22% 0.006 60);
    --mu-12-ink-2: #57514b;
    --mu-12-ink-2: oklch(45% 0.01 62);
    --mu-12-scrim: rgba(24, 22, 20, 0.48);
    --mu-12-acc: #0d6355;
    --mu-12-acc: oklch(43% 0.09 175);
    --mu-12-acc-ink: #ffffff;
    --mu-12-acc-ink: oklch(100% 0 0);
  }
}

@media (prefers-color-scheme: light) {
  .mu-12:has(.mu-12__theme-input:checked) {
    --mu-12-e0: #1b1917;
    --mu-12-e0: oklch(22% 0.006 60);
    --mu-12-e1: #232120;
    --mu-12-e1: oklch(26% 0.006 60);
    --mu-12-e2: #2c2a28;
    --mu-12-e2: oklch(30% 0.006 60);
    --mu-12-e3: #363331;
    --mu-12-e3: oklch(34% 0.007 60);
    --mu-12-edge: #4a4643;
    --mu-12-edge: oklch(42% 0.008 60);
    --mu-12-ink: #f5f2ef;
    --mu-12-ink: oklch(96% 0.005 70);
    --mu-12-ink-2: #b3aca6;
    --mu-12-ink-2: oklch(75% 0.009 65);
    --mu-12-scrim: rgba(8, 7, 6, 0.62);
    --mu-12-acc: #8fd4c4;
    --mu-12-acc: oklch(84% 0.09 175);
    --mu-12-acc-ink: #12201d;
    --mu-12-acc-ink: oklch(19% 0.02 175);
  }
}

.mu-12__sr {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.mu-12__theme {
  position: absolute;
  inset-block-start: 1rem;
  inset-inline-end: 1rem;
  z-index: 5;
}

.mu-12__theme-input {
  position: absolute;
  inset: 0;
  inline-size: 44px;
  block-size: 44px;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.mu-12__theme-label {
  display: grid;
  place-items: center;
  inline-size: 44px;
  block-size: 44px;
  border: 1px solid var(--mu-12-edge);
  border-radius: 50%;
  background: var(--mu-12-e2);
  color: var(--mu-12-ink-2);
  cursor: pointer;
}

.mu-12__theme-label svg {
  inline-size: 20px;
  block-size: 20px;
}

.mu-12__theme-input:focus-visible + .mu-12__theme-label {
  outline: 2px solid var(--mu-12-acc);
  outline-offset: 2px;
}

.mu-12__stage {
  max-inline-size: 62rem;
  margin-inline: auto;
  padding: 4rem 1.25rem 5rem;
  display: grid;
  gap: 2.5rem;
  grid-template-columns: minmax(0, 1fr);
}

.mu-12__ladder {
  min-inline-size: 0;
}

.mu-12__level {
  min-inline-size: 0;
  display: grid;
  gap: 0.75rem;
  border-radius: 10px;
  border-block-start: 1px solid var(--mu-12-edge);
}

@supports (color: color-mix(in oklab, red, blue)) {
  .mu-12__level {
    border-block-start-color: color-mix(in oklab, var(--mu-12-edge) 85%, var(--mu-12-ink));
  }
}

.mu-12__level--0 {
  background: var(--mu-12-e0);
  padding: 1.5rem;
  border-block-start-color: transparent;
}

.mu-12__level--1 {
  background: var(--mu-12-e1);
  padding: calc(1.5rem + var(--mu-12-pad-step));
}

.mu-12__level--2 {
  background: var(--mu-12-e2);
  padding: calc(1.5rem + var(--mu-12-pad-step) * 2);
}

.mu-12__level-head {
  display: grid;
  gap: 2px;
  min-inline-size: 0;
}

.mu-12__level-title {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: -0.008em;
}

.mu-12__level-spec {
  margin: 0;
  font-family: var(--mu-12-mono);
  font-size: 0.75rem;
  color: var(--mu-12-ink-2);
  font-variant-numeric: tabular-nums;
}

.mu-12__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding-block-start: 0.25rem;
}

.mu-12__btn {
  min-block-size: 44px;
  min-inline-size: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding-inline: 1rem;
  border-radius: 8px;
  border: 1px solid var(--mu-12-edge);
  background: transparent;
  color: var(--mu-12-ink);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
}

.mu-12__btn:focus-visible {
  outline: 2px solid var(--mu-12-acc);
  outline-offset: 2px;
}

.mu-12__btn--primary {
  background: var(--mu-12-acc);
  border-color: var(--mu-12-acc);
  color: var(--mu-12-acc-ink);
  font-weight: 600;
}

.mu-12__overlay-demo {
  display: grid;
  gap: 0.875rem;
  min-inline-size: 0;
}

.mu-12__scrim {
  min-inline-size: 0;
  padding: 2rem 1rem;
  border-radius: 10px;
  background: var(--mu-12-scrim);
  display: grid;
  place-items: center;
}

.mu-12__dialog {
  min-inline-size: 0;
  inline-size: 100%;
  max-inline-size: 26rem;
  display: grid;
  gap: 0.625rem;
  padding: calc(1.5rem + var(--mu-12-pad-step) * 3);
  background: var(--mu-12-e3);
  border-radius: 12px;
  border-block-start: 1px solid var(--mu-12-edge);
}

@media (min-width: 48rem) {
  .mu-12__stage {
    padding-inline: 3rem;
  }

  .mu-12__level--0 {
    padding: 2rem;
  }

  .mu-12__level--1 {
    padding: calc(2rem + var(--mu-12-pad-step));
  }

  .mu-12__level--2 {
    padding: calc(2rem + var(--mu-12-pad-step) * 2);
  }

  .mu-12__scrim {
    padding: 3rem;
  }
}
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 Minimalist UI 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: Elevation Without Shadows or Gradients
Source: https://codefronts.com/design-styles/css-minimalist-ui/elevation-without-shadows-or-gradients/

Three elevation levels rendered with surface tone, spacing and a single hairline &mdash; no shadow stack, no gradients. It also states the limit honestly: without shadows a floating element loses its depth cue, so the overlay here still ships a scrim, and the demo explains why that is not a contradiction.
## HTML
```html
<section class="mu-12" aria-labelledby="mu-12-title">
  <h2 class="mu-12__sr" id="mu-12-title">Tonal elevation levels</h2>
  <div class="mu-12__theme">
    <input class="mu-12__theme-input" type="checkbox" id="mu-12-theme">
    <label class="mu-12__theme-label" for="mu-12-theme">
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 4a8 8 0 0 0 0 16Z" fill="currentColor"/></svg>
      <span class="mu-12__sr">Toggle dark theme</span>
    </label>
  </div>

  <div class="mu-12__stage">

    <div class="mu-12__ladder">
      <article class="mu-12__level mu-12__level--0" aria-labelledby="mu-12-l0">
        <header class="mu-12__level-head">
          <h3 class="mu-12__level-title" id="mu-12-l0">Level 0 &mdash; page</h3>
          <p class="mu-12__level-spec">base tone &middot; no edge &middot; pad 24px</p>
        </header>

        <article class="mu-12__level mu-12__level--1" aria-labelledby="mu-12-l1">
          <header class="mu-12__level-head">
            <h4 class="mu-12__level-title" id="mu-12-l1">Level 1 &mdash; panel</h4>
            <p class="mu-12__level-spec">+4% lightness &middot; top hairline &middot; pad 28px</p>
          </header>

          <article class="mu-12__level mu-12__level--2" aria-labelledby="mu-12-l2">
            <header class="mu-12__level-head">
              <h5 class="mu-12__level-title" id="mu-12-l2">Level 2 &mdash; raised</h5>
              <p class="mu-12__level-spec">+8% lightness &middot; top hairline &middot; pad 32px</p>
            </header>
            <div class="mu-12__actions">
              <button class="mu-12__btn mu-12__btn--primary" type="button">Save workspace</button>
              <button class="mu-12__btn" type="button">Discard</button>
            </div>
          </article>
        </article>
      </article>
    </div>

    <section class="mu-12__overlay-demo" aria-labelledby="mu-12-l3">
      <div class="mu-12__scrim">
        <div class="mu-12__dialog">
          <h3 class="mu-12__level-title" id="mu-12-l3">Level 3 &mdash; overlay</h3>
          <p class="mu-12__level-spec">+12% lightness &middot; top hairline &middot; scrim required</p>
          <div class="mu-12__actions">
            <button class="mu-12__btn mu-12__btn--primary" type="button">Confirm transfer</button>
            <button class="mu-12__btn" type="button">Cancel</button>
          </div>
        </div>
      </div>
    </section>
  </div>
</section>
```
## CSS
```css
.mu-12 {
  --mu-12-pad-step: 0.25rem;
  --mu-12-round: ui-rounded, "SF Pro Rounded", "Hiragino Maru Gothic ProN", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mu-12-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  --mu-12-e0: #1b1917;
  --mu-12-e0: oklch(22% 0.006 60);
  --mu-12-e1: #232120;
  --mu-12-e1: oklch(26% 0.006 60);
  --mu-12-e2: #2c2a28;
  --mu-12-e2: oklch(30% 0.006 60);
  --mu-12-e3: #363331;
  --mu-12-e3: oklch(34% 0.007 60);
  --mu-12-edge: #4a4643;
  --mu-12-edge: oklch(42% 0.008 60);
  --mu-12-ink: #f5f2ef;
  --mu-12-ink: oklch(96% 0.005 70);
  --mu-12-ink-2: #b3aca6;
  --mu-12-ink-2: oklch(75% 0.009 65);
  --mu-12-scrim: rgba(8, 7, 6, 0.62);
  --mu-12-acc: #8fd4c4;
  --mu-12-acc: oklch(84% 0.09 175);
  --mu-12-acc-ink: #12201d;
  --mu-12-acc-ink: oklch(19% 0.02 175);
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  position: relative;
  box-sizing: border-box;
  background: var(--mu-12-e0);
  color: var(--mu-12-ink);
  font-family: var(--mu-12-round);
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}

.mu-12 *,
.mu-12 *::before,
.mu-12 *::after {
  box-sizing: border-box;
}

@media (prefers-color-scheme: light) {
  .mu-12 {
    --mu-12-e0: #eeecea;
    --mu-12-e0: oklch(94% 0.004 70);
    --mu-12-e1: #f5f3f1;
    --mu-12-e1: oklch(96.5% 0.004 70);
    --mu-12-e2: #fbfaf9;
    --mu-12-e2: oklch(98.5% 0.003 70);
    --mu-12-e3: #ffffff;
    --mu-12-e3: oklch(100% 0 0);
    --mu-12-edge: #cfcac5;
    --mu-12-edge: oklch(84% 0.007 70);
    --mu-12-ink: #1b1917;
    --mu-12-ink: oklch(22% 0.006 60);
    --mu-12-ink-2: #57514b;
    --mu-12-ink-2: oklch(45% 0.01 62);
    --mu-12-scrim: rgba(24, 22, 20, 0.48);
    --mu-12-acc: #0d6355;
    --mu-12-acc: oklch(43% 0.09 175);
    --mu-12-acc-ink: #ffffff;
    --mu-12-acc-ink: oklch(100% 0 0);
  }
}

.mu-12[data-theme="light"],
.mu-12:has(.mu-12__theme-input:checked) {
  --mu-12-e0: #eeecea;
  --mu-12-e0: oklch(94% 0.004 70);
  --mu-12-e1: #f5f3f1;
  --mu-12-e1: oklch(96.5% 0.004 70);
  --mu-12-e2: #fbfaf9;
  --mu-12-e2: oklch(98.5% 0.003 70);
  --mu-12-e3: #ffffff;
  --mu-12-e3: oklch(100% 0 0);
  --mu-12-edge: #cfcac5;
  --mu-12-edge: oklch(84% 0.007 70);
  --mu-12-ink: #1b1917;
  --mu-12-ink: oklch(22% 0.006 60);
  --mu-12-ink-2: #57514b;
  --mu-12-ink-2: oklch(45% 0.01 62);
  --mu-12-scrim: rgba(24, 22, 20, 0.48);
  --mu-12-acc: #0d6355;
  --mu-12-acc: oklch(43% 0.09 175);
  --mu-12-acc-ink: #ffffff;
  --mu-12-acc-ink: oklch(100% 0 0);
}

@media (prefers-color-scheme: dark) {
  .mu-12:has(.mu-12__theme-input:checked) {
    --mu-12-e0: #eeecea;
    --mu-12-e0: oklch(94% 0.004 70);
    --mu-12-e1: #f5f3f1;
    --mu-12-e1: oklch(96.5% 0.004 70);
    --mu-12-e2: #fbfaf9;
    --mu-12-e2: oklch(98.5% 0.003 70);
    --mu-12-e3: #ffffff;
    --mu-12-e3: oklch(100% 0 0);
    --mu-12-edge: #cfcac5;
    --mu-12-edge: oklch(84% 0.007 70);
    --mu-12-ink: #1b1917;
    --mu-12-ink: oklch(22% 0.006 60);
    --mu-12-ink-2: #57514b;
    --mu-12-ink-2: oklch(45% 0.01 62);
    --mu-12-scrim: rgba(24, 22, 20, 0.48);
    --mu-12-acc: #0d6355;
    --mu-12-acc: oklch(43% 0.09 175);
    --mu-12-acc-ink: #ffffff;
    --mu-12-acc-ink: oklch(100% 0 0);
  }
}

@media (prefers-color-scheme: light) {
  .mu-12:has(.mu-12__theme-input:checked) {
    --mu-12-e0: #1b1917;
    --mu-12-e0: oklch(22% 0.006 60);
    --mu-12-e1: #232120;
    --mu-12-e1: oklch(26% 0.006 60);
    --mu-12-e2: #2c2a28;
    --mu-12-e2: oklch(30% 0.006 60);
    --mu-12-e3: #363331;
    --mu-12-e3: oklch(34% 0.007 60);
    --mu-12-edge: #4a4643;
    --mu-12-edge: oklch(42% 0.008 60);
    --mu-12-ink: #f5f2ef;
    --mu-12-ink: oklch(96% 0.005 70);
    --mu-12-ink-2: #b3aca6;
    --mu-12-ink-2: oklch(75% 0.009 65);
    --mu-12-scrim: rgba(8, 7, 6, 0.62);
    --mu-12-acc: #8fd4c4;
    --mu-12-acc: oklch(84% 0.09 175);
    --mu-12-acc-ink: #12201d;
    --mu-12-acc-ink: oklch(19% 0.02 175);
  }
}

.mu-12__sr {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.mu-12__theme {
  position: absolute;
  inset-block-start: 1rem;
  inset-inline-end: 1rem;
  z-index: 5;
}

.mu-12__theme-input {
  position: absolute;
  inset: 0;
  inline-size: 44px;
  block-size: 44px;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.mu-12__theme-label {
  display: grid;
  place-items: center;
  inline-size: 44px;
  block-size: 44px;
  border: 1px solid var(--mu-12-edge);
  border-radius: 50%;
  background: var(--mu-12-e2);
  color: var(--mu-12-ink-2);
  cursor: pointer;
}

.mu-12__theme-label svg {
  inline-size: 20px;
  block-size: 20px;
}

.mu-12__theme-input:focus-visible + .mu-12__theme-label {
  outline: 2px solid var(--mu-12-acc);
  outline-offset: 2px;
}

.mu-12__stage {
  max-inline-size: 62rem;
  margin-inline: auto;
  padding: 4rem 1.25rem 5rem;
  display: grid;
  gap: 2.5rem;
  grid-template-columns: minmax(0, 1fr);
}

.mu-12__ladder {
  min-inline-size: 0;
}

.mu-12__level {
  min-inline-size: 0;
  display: grid;
  gap: 0.75rem;
  border-radius: 10px;
  border-block-start: 1px solid var(--mu-12-edge);
}

@supports (color: color-mix(in oklab, red, blue)) {
  .mu-12__level {
    border-block-start-color: color-mix(in oklab, var(--mu-12-edge) 85%, var(--mu-12-ink));
  }
}

.mu-12__level--0 {
  background: var(--mu-12-e0);
  padding: 1.5rem;
  border-block-start-color: transparent;
}

.mu-12__level--1 {
  background: var(--mu-12-e1);
  padding: calc(1.5rem + var(--mu-12-pad-step));
}

.mu-12__level--2 {
  background: var(--mu-12-e2);
  padding: calc(1.5rem + var(--mu-12-pad-step) * 2);
}

.mu-12__level-head {
  display: grid;
  gap: 2px;
  min-inline-size: 0;
}

.mu-12__level-title {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: -0.008em;
}

.mu-12__level-spec {
  margin: 0;
  font-family: var(--mu-12-mono);
  font-size: 0.75rem;
  color: var(--mu-12-ink-2);
  font-variant-numeric: tabular-nums;
}

.mu-12__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding-block-start: 0.25rem;
}

.mu-12__btn {
  min-block-size: 44px;
  min-inline-size: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding-inline: 1rem;
  border-radius: 8px;
  border: 1px solid var(--mu-12-edge);
  background: transparent;
  color: var(--mu-12-ink);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
}

.mu-12__btn:focus-visible {
  outline: 2px solid var(--mu-12-acc);
  outline-offset: 2px;
}

.mu-12__btn--primary {
  background: var(--mu-12-acc);
  border-color: var(--mu-12-acc);
  color: var(--mu-12-acc-ink);
  font-weight: 600;
}

.mu-12__overlay-demo {
  display: grid;
  gap: 0.875rem;
  min-inline-size: 0;
}

.mu-12__scrim {
  min-inline-size: 0;
  padding: 2rem 1rem;
  border-radius: 10px;
  background: var(--mu-12-scrim);
  display: grid;
  place-items: center;
}

.mu-12__dialog {
  min-inline-size: 0;
  inline-size: 100%;
  max-inline-size: 26rem;
  display: grid;
  gap: 0.625rem;
  padding: calc(1.5rem + var(--mu-12-pad-step) * 3);
  background: var(--mu-12-e3);
  border-radius: 12px;
  border-block-start: 1px solid var(--mu-12-edge);
}

@media (min-width: 48rem) {
  .mu-12__stage {
    padding-inline: 3rem;
  }

  .mu-12__level--0 {
    padding: 2rem;
  }

  .mu-12__level--1 {
    padding: calc(2rem + var(--mu-12-pad-step));
  }

  .mu-12__level--2 {
    padding: calc(2rem + var(--mu-12-pad-step) * 2);
  }

  .mu-12__scrim {
    padding: 3rem;
  }
}
```

How this works

Shadows are one way to say “closer”, not the only way. A tonal ramp does the same job: on a light theme each raised level gets slightly lighter, on a dark theme slightly lighter again, and the eye reads the lighter surface as nearer. The advantage over a shadow stack is that tone costs nothing to paint, never smears on a low-DPI screen, and does not need a different blur radius for every level.

Add one hairline, at the top edge only. A physical raised surface catches light on the edge facing the light source. A single 1px line at the block-start edge, one or two steps lighter than the surface, reproduces that cue precisely — and unlike a full border it does not outline the shape, so the surface still reads as a plane rather than a box. This is the single highest-value detail in tonal elevation.

Spacing is the third signal. Give each higher level slightly more internal padding. A closer object subtends a larger angle, so more generous interior space reads as proximity — it is a weak signal on its own and a strong one stacked with tone and hairline.

The limit, stated plainly. Tone works for levels that share the page: cards, panels, sticky bars. It does not work for something floating over arbitrary content, because you cannot guarantee the tone underneath. A dialog over a photograph is invisible if its only cue is being 4% lighter. That is why the overlay level here has a scrim: the scrim is not a failure of the technique, it is the correct cue for the one case tone cannot cover. And note that this ramp is rebuilt per theme rather than inverted — a dark ramp that flips the light one puts your lowest surface at pure black, which leaves nowhere to go downward.

Make it yours

  • Retune the ramp with --mu-12-e1 through --mu-12-e3; keep steps at 3-5% lightness so each level is distinguishable but not stripey.
  • Turn the top hairline off by setting --mu-12-edge to transparent and see how much of the depth it was carrying.
  • Change --mu-12-scrim opacity to tune the overlay separation for a busier page behind it.
  • Add a fourth level by extending the ramp; the padding step follows from --mu-12-pad-step.
  • Swap the hairline for a bottom-edge shadow-free rule if your light source convention is from below.
  • Recolour by editing the two --mu-12-acc declarations, hex first then oklch.

Gotchas — read before shipping

  • Tonal steps under about 3% lightness are invisible on a phone in daylight; steps over 8% read as stripes rather than layers. Measure, do not eyeball.
  • A floating element over unknown content needs a scrim. Tone alone cannot separate a dialog from a photograph.
  • Inverting a light ramp puts the dark theme's base at pure black, which leaves no room for a level below and halates against light text. Rebuild the ramp per theme.
  • Using opacity layers instead of solid tones means overlapping surfaces compound unpredictably. Declare each level as an opaque colour.

Browser support

ChromeSafariFirefoxEdge
111+16.2+113+111+

color-mix(in oklab, …) sets the Safari 16.2 and Firefox 113 floors and is used only for the hairline tint, inside an @supports gate with a flat sRGB hairline declared first — engines without it get the same three levels with a slightly less precise edge. oklch() needs Chrome 111 / Safari 15.4 / Firefox 113. The :has() theme toggle needs Firefox 121; below that the switch stops working and the operating-system scheme applies.

Techniques used in this demo

Search CodeFronts

Loading…