Shadows
Surfaces lift with a hairline edge, not a border. Four steps cover everything from a card to a dialog.
Edges, not borders
Every step draws its own edge first: a 0.5px line that stays crisp on high-density screens. So an elevated surface needs no border and no ring. Add one anyway and you get two lines a pixel apart.
Do
The raised step alone: one clean edge, then a little depth.
Donโt
A border under the shadowโs edge reads as a double line and a heavier button.
Pick the step by its job
Choose by what the surface does, not by how much depth you want. Filled buttons and checked boxes stay flat: a shadow beside a solid fill reads as a halo.
Flat cards and swatches that need an edge
Resting cards, outline buttons, the selected segment
Menus, popovers, selects, tooltips, toasts
Dialogs, sheets, drawers, the command palette
Build it
Apply a step with a shadow utility. Nothing else is needed for the edge:
elevation.tsx
1234<section className="rounded-lg bg-surface p-4 shadow-(--shadow-border)">Flat card</section><section className="rounded-lg bg-surface p-4 shadow-(--shadow-raised)">Resting card</section><div className="rounded-lg bg-popover p-1 shadow-(--shadow-overlay)">Menu</div><div className="rounded-xl bg-popover p-5 shadow-(--shadow-dialog)">Dialog</div>The steps live in the theme, so every installed component follows when you change one.
styles/jzb-theme.css
123456789101112:root { --shadow-raised: 0 0 0 0.5px color-mix(in oklch, var(--neutral-1000) 8%, transparent), 0 1px 1px -1px color-mix(in oklch, var(--neutral-1000) 10%, transparent), 0 1px 2px color-mix(in oklch, var(--neutral-1000) 5%, transparent);}
.dark { --shadow-raised: 0 0 0 0.5px color-mix(in oklch, var(--neutral-0) 12%, transparent), 0 1px 2px color-mix(in oklch, var(--neutral-1000) 30%, transparent);}