Skip to content
ssntl/ui

Smooth shadows

Five-layer shadows that fall off like real light. Size, intensity, tint and a hairline ring.

size 0.5
size 1
size 2
size 3 · hairline

Installation

$ pnpm dlx shadcn@latest add @ssntl/shadow-smooth

Usage

css
/* app/globals.css */
@import "../styles/ssntl/shadow-smooth.css";

/* In your JSX */
<div class="shadow-smooth">…</div>
<div class="shadow-smooth shadow-size-2 shadow-intensity-150">…</div>
<div class="shadow-smooth shadow-tint-blue-600 shadow-hairline">…</div>
  • Five layers that double in distance and blur: the shadow falls off like real light, not one blur.
  • Two numbers control it: shadow-size-{n} scales the stack and shadow-intensity-{n} its opacity in %.
  • In dark mode the stack is 3× stronger so it still reads; it follows data-theme and the OS.
  • Only for what floats. Areas are separated by tone, not shadow.

Code

styles/ssntl/shadow-smooth.css
/* Smooth shadows: five layers that double in distance and blur, so the shadow
   falls off like real light instead of one hard blur.

     shadow-smooth                    the stack, size 1, intensity 100
     shadow-size-{n}                  scale the stack (0.5, 2, 3…)
     shadow-intensity-{n}             opacity in % (0–300)
     shadow-tint-{color}              tint the stack with a palette color
     shadow-hairline                  add a hairline ring (0.5px on retina)

   Dark surfaces need more shadow to read, so the stack is 3× stronger in dark. */

:root,
[data-theme="light"],
.light {
  --shadow-strength: 1;
}
[data-theme="dark"],
.dark {
  --shadow-strength: 3;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"], .light) {
    --shadow-strength: 3;
  }
}

@utility shadow-smooth {
  --_s: var(--shadow-size, 1);
  --_a: calc(3% * var(--shadow-intensity, 1) * var(--shadow-strength, 1));
  --_c: var(--shadow-color, #000);
  box-shadow:
    0 0 0 var(--shadow-ring, 0px)
      color-mix(in oklab, var(--_c) calc(var(--_a) * 3), transparent),
    0 calc(1px * var(--_s)) calc(2px * var(--_s))
      color-mix(in oklab, var(--_c) var(--_a), transparent),
    0 calc(2px * var(--_s)) calc(4px * var(--_s))
      color-mix(in oklab, var(--_c) var(--_a), transparent),
    0 calc(4px * var(--_s)) calc(8px * var(--_s))
      color-mix(in oklab, var(--_c) var(--_a), transparent),
    0 calc(8px * var(--_s)) calc(16px * var(--_s))
      color-mix(in oklab, var(--_c) var(--_a), transparent),
    0 calc(16px * var(--_s)) calc(32px * var(--_s))
      color-mix(in oklab, var(--_c) var(--_a), transparent);
}

@utility shadow-size-* {
  --shadow-size: --value(number);
}

@utility shadow-intensity-* {
  --shadow-intensity: calc(--value(integer) / 100);
}

@utility shadow-tint-* {
  --shadow-color: --value(--color-*, [color]);
}

@utility shadow-hairline {
  --shadow-ring: 1px;
  @media (min-resolution: 2dppx) {
    --shadow-ring: 0.5px;
  }
}