Overview
In editorial UIs, depth is something you spend, not something you decorate with. Most surfaces in Flatpay are flat. They sit on the page; a hairline border and a subtle background tint tell you where one ends and another begins.
Shadow shows up when a surface leaves the page — when it is summoned for a moment, when it captures focus, when it has to read as a layer above the rest. A page full of equally-shadowed cards looks templated. A page where exactly one thing is shadowed reads as deliberate.
The first principle
Borders before shadows. If a surface lives in the page's normal flow, give it a 1-pixel border and stop. Reach for shadow-* only when the surface is genuinely floating above the rest of the page.
Surfaces first
The same card, two ways. The bordered version belongs on the page. The shadowed version pretends it's floating, and the lie compounds the moment you put two of them next to each other.
In-flow surface
PreferredBorder + tint
A card embedded in the page. The 1-pixel border and a small bg-tint shift do the work. No shadow needed — the page stays flat and orderly.
In-flow surface
AvoidShadow as the only cue
Shadow without a border reads as floating UI on the page itself. Tempting, then noisy at scale — a dashboard of these looks templated, not designed.
The shadow scale
We use Tailwind's default shadow scale verbatim — seven rungs from flat to fully detached. Each rung implies a different relationship to the page beneath it. Pick the lowest rung that gets the job done.
shadow.noneshadow-noneThe default. Cards, list rows, table cells, sections. Flat surfaces hold the page together.
shadow.xsshadow-xsHairline lift — hovered list rows, pressed buttons leaving the surface for a beat.
shadow.smshadow-smHover cards, inline popovers, autocomplete suggestions sitting on the input.
shadow.mdshadow-mdDropdown menus, select listboxes, date pickers, tooltips with context.
shadow.lgshadow-lgSide sheets, command palettes, popover dialogs anchored to a trigger.
shadow.xlshadow-xlModal dialogs sitting on a dimmed scrim. The page behind goes quiet; the dialog gets the lift.
shadow.2xlshadow-2xlReserved. Toast stacks, app-level overlays, anything that must read as a layer above the modal layer itself.
Floating UI
The four canonical floating surfaces: hover cards (the lightest), dropdowns (anchored to a trigger), popovers (anchored, larger, may contain controls), and dialogs (modal, demand a decision). Each gets the lowest shadow that still reads as detached.
- All prototypes
- Demo-ready
- Building
- Archived
Dropdown menu · shadow-md
Anchored to a trigger, lives one layer above the page. The shadow says "temporary," not "structural."
Lucky Garden
Demo-ready · Low risk
Onboarding flow for new merchants. Last synced 3h ago.
Hover card · shadow-sm
The lightest lift. Lives on the surface it's anchored to; doesn't claim its own page region.
Archive Lucky Garden?
The repo stays — the prototype is hidden from the catalog.
Dialog · shadow-xl + scrim
The page is dimmed; the dialog gets the highest practical lift. Reserve this treatment for decisions the user must answer.
Backdrops belong to dialogs only
A scrim — the dimmed sheet behind a dialog — signals that everything else is paused. Don't add it to popovers, dropdowns, or hover cards. Those surfaces co-exist with the page; the dialog interrupts it.
Stacking order
Five layers cover every screen we ship. They climb in steps of 10 so a future layer can slot in between without renumbering everything else.
z-0pageCards, list rows, content sections — everything in normal flow.
z-10stickyIn-page sticky elements — table headers, secondary tabs.
z-20sidebarLeft navigation; lifts above scroll content but sits under chrome.
z-30headerTop app bar, page hero pin. Above the sidebar so menus extend cleanly.
z-40overlayDropdowns, popovers, hover cards, autocomplete listboxes.
z-50dialogModal dialogs, side sheets, command palettes — UI that captures focus.
z-50*toastSame layer as dialog; rendered last so notifications survive a dialog open.
* Toasts and dialogs share the dialog layer; render order, not z-index, decides which stacks on top.
Interactive elevation
Interactive surfaces don't just change color on hover — they shift relative to the page. The lift is small, the press is smaller, and both finish in under 150ms. The motion is what tells the user the surface responded; the shadow is just the byproduct.
Resting
Default state — flat against the page.
Hover
Cursor on the surface. Border tightens, an xs shadow hints the lift.
Pressed
Pointer down. Returns to the page; the shadow drops away.
In code
Tailwind utilities, applied in line. The shadow class is the token; no extra alias layer.
tsx
// Default card — flat, bordered
<article className="rounded-lg border bg-card p-5">
…
</article>
// Dropdown menu — anchored, floating
<div role="menu" className="rounded-lg border bg-background p-2 shadow-md">
…
</div>
// Hover card — light lift
<aside className="rounded-lg border bg-background p-3 shadow-sm">
…
</aside>
// Modal dialog — highest practical lift, with scrim
<div className="fixed inset-0 z-50">
<div aria-hidden className="absolute inset-0 bg-foreground/35" />
<div role="dialog" className="absolute inset-x-6 top-1/2 -translate-y-1/2 rounded-xl border bg-background p-6 shadow-xl">
…
</div>
</div>
// Interactive lift — restful → hover → pressed
<button
className="rounded-md border bg-background px-3 py-2 transition
hover:border-foreground/40 hover:shadow-xs
active:shadow-none"
>
Open prototype
</button>Don'ts
- Don't shadow in-flow surfaces. Cards, list rows, sections, and table cells are flat. A border carries the edge.
- Don't skip rungs. If a dropdown wants
shadow-md, don't reach forshadow-xlto make it "more visible." Bigger shadow doesn't mean bigger importance — it means further from the page. - Don't stack shadows. A shadowed card inside a shadowed surface inside a dialog reads as a layered cake. Pick the layer that owns the lift; everything inside it stays flat.
- Don't hard-code z-index. Use the named layers (
z-0throughz-50). Numbers likez-[9999]are a symptom of an unowned stacking order. - Don't replace shadow with glow. No coloured drop shadows, no neon halos, no "brand-tinted" glows. Lift is described in greyscale; the brand is described elsewhere.