Foundations
Spatial language
Depth is a system, not a decoration. One depth scale, four elevation rungs and a single press gesture cover every surface in the library — from a Button to a Dialog to a table row — so a product can change how deep it feels with one attribute, and nothing has to be re-tuned per component.
The change, side by side
Before V4.0 an interactive surface answered the pointer with Tailwind's own grey shadow — a value the theme could not move — and a fixed one-pixel lift. The spatial language replaces both with tokens: the shadow is a rung of the theme's ladder, and the lift is a distance the personality (and data-space) owns.
Before · pre-V4 recipe
A default shadow that never changes between themes, and a hover transform that ignores reduced motion and personality.
After · @luc/ui
A theme-owned rung, a distance the personality scales, and an instant settle when reduced motion is requested.
Four rungs, one well
A surface names its layer instead of choosing a shadow value. Because the rung is a token, a palette change, a personality change and data-space all move it together — which is what makes the library read as one object.
shadow-rest
Resting
The default state of a card, a control or a row. Quiet by construction.
shadow-hover
Answered
The same surface once a pointer or a keyboard highlight reaches it.
shadow-layer-2
Floating
Menus, popovers, tooltips and hover cards — detached from the page.
shadow-layer-3
Modal
Dialogs, sheets and the command palette: the highest layer in the system.
shadow-well
Sunken
A surface the content sits inside: text fields, progress tracks, segmented controls, a pressed toggle.
One attribute, a whole subtree
data-space scales every distance in a subtree without touching a single component: flat for a print view or a dense table, layered by default, deep for a hero or a gallery. Reduced motion keeps the end state and drops the trip.
data-space="layered"Card
--spatial-depth: 1
Row
--spatial-depth: 1
Action
--spatial-depth: 1
Overlay
--spatial-depth: 1
States answer in the same language
Hover rises, press sinks, focus lights, disabled flattens. The same three gestures appear on a Button, a Card, a menu row and a calendar day — which is why a page built from many families still feels like one surface.
Hover a card
What to use
Everything below is public: a consumer can build its own surface with the same rungs, and it will follow the palette, the personality and data-space exactly as the built-in components do.
| Prop | Type | Default | Notes |
|---|---|---|---|
--spatial-depth | 0 · 1 · 1.6 | — | Set by data-space=flat|layered|deep; a personality sets the resting unit. |
--spatial-lift | length | — | calc(lift-unit × depth) — how far a surface rises to the pointer. |
--spatial-sink | length | — | How far it settles under the finger, paired with --spatial-press-scale. |
--elevation-rest / -hover / -active / -float | box-shadow | — | The four rungs every component names instead of picking a shadow. |
--layer-2-shadow / --layer-3-shadow | box-shadow | — | Two stacked steps: a floating menu, then a modal surface. |
--well-shadow | box-shadow | — | Sunken: inputs, progress tracks, segmented controls, selected toggles. |
luc-rest · luc-hover · luc-float · luc-float-high · luc-well | utility | — | The rungs as classes, for a consumer styling its own surface. |
Reduced motion
The travel is transform-only and one-shot, so the clamp in base.css lands on the end state rather than a half-raised surface.
Hover no longer animates a lift, press no longer springs, and the disclosure panels open instantly. Nothing disappears: contrast, focus visibility, layout and every state remain exactly as legible as the animated version, because the animation was never carrying information.