Skip to content

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

Badge

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

Live

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

--elevation-sm--elevation-restluc-rest

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.

Component props
PropTypeDefaultNotes
--spatial-depth0 · 1 · 1.6—Set by data-space=flat|layered|deep; a personality sets the resting unit.
--spatial-liftlength—calc(lift-unit × depth) — how far a surface rises to the pointer.
--spatial-sinklength—How far it settles under the finger, paired with --spatial-press-scale.
--elevation-rest / -hover / -active / -floatbox-shadow—The four rungs every component names instead of picking a shadow.
--layer-2-shadow / --layer-3-shadowbox-shadow—Two stacked steps: a floating menu, then a modal surface.
--well-shadowbox-shadow—Sunken: inputs, progress tracks, segmented controls, selected toggles.
luc-rest · luc-hover · luc-float · luc-float-high · luc-wellutility—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.

Luc UI v1.0Spatial languagedark theme