Skip to content

Motion

Spatial motion

Depth as a coordinate system, not a decoration. Six capability families, each one a public @luc/ui export, all driven by the control board below — change the depth, switch the material, freeze the orbit, and watch the page answer.

Control board

One panel drives every tile, so two treatments can be compared under identical settings instead of hunting for per-demo knobs.

Depth

24px

Tilt

6°

Orbit speed

1.0×

Intensity scopes how loud everything below it may be. Reduced motion still resolves every level to none, and the arrangements stay intact.

Surfaces, materials and layered depth

ParallaxScene publishes one pointer offset as custom properties; each ParallaxLayer multiplies it by its own depth. One rAF loop, zero React renders per frame, and the whole scene still composes when nothing moves.

ParallaxScene

<ParallaxScene depth tilt> · <ParallaxLayer depth z axis>
24px · 6°
depth -1.4

workspace

analyst / eu-west

depth 0

Text stays still

Moving the layer you read is the fastest way to make a page feel cheap. Depth belongs to decoration.

Readable content sits at depth 0 and never shifts. The grid and the aurora recede, the workspace card travels with the pointer, and the badge moves against it — the classic foreground read, all from the same two numbers.

One rAF loop

The scene owns the frame loop and stops it when the pointer leaves and the motion settles.

No per-frame renders

Positions travel as inherited custom properties, so layers are ordinary divs.

Flat, but composed

Without a pointer every offset resolves to zero and the layers keep their translateZ separation.

Geometric motion

An orbit is a layout: things are arranged on rings, the rings turn, and any seat can be a real control. Rotation runs on the compositor, and every label counter-rotates at the ring's exact period so it stays upright.

OrbitField

<OrbitField speed paused> · <OrbitFieldRing radius duration direction> · <OrbitFieldNode angle onSelect>
1.0× turn

APAC

412

+6.4%

The outer ring is trackless — a ring without a visible track still arranges its seats, which is how a second tier is built without drawing a circle nobody asked for.

Why it reads as a system

Seats are real buttons: click, Tab and Enter all work, and aria-pressed carries the choice.

A seat is named with its value, so the orbit is fully legible to a screen reader.

Freezing the orbit keeps the arrangement — waiting for a turning chip is not an interaction.

Select a region on the middle ring; the hub reads the same identity.

Morphing and continuity

ExpandableSurface keeps a compact summary and its expanded detail in one grid cell with one shared layout identity, so the surface grows out of its own rectangle and settles back — in flow, with no portal, no scroll lock and no focus trap.

Design rules

In flow, never a modal: a dashboard card that reveals detail should not take the page away.

The trigger stays mounted — it is the focus target we return to and the element that carries aria-expanded.

Reduced motion drops the shared layout rather than slowing it, so the surface simply swaps.

Rejected: a full-screen morphing overlay for this content. It hides the surrounding context that makes the detail meaningful.

Kinetic typography

KineticHeadline animates a change of words with a direction and a reading rhythm. Word granularity, real text nodes, and one accessible string — never a character split across nine spans.

<KineticHeadline text mode stagger live />

Built for dense interfaces

The animated layer is aria-hidden; a visually hidden copy carries the text and announces it politely when live is on, so a screen reader hears a sentence rather than a spelling.

The same string, static

Built for dense interfaces

Read both. The kinetic version must never be harder to read than the plain one — a headline that only works in motion is not a headline.

Depth and spatial navigation

CardStack makes depth the navigation: the active card is largest and closest, the deck behind it recedes on three axes, and stepping moves the whole deck. Arrow keys, Home and End, and a 48px touch drag all work.

<CardStack visible offsetY shrink spread> · <CardStackItem> · <CardStackControls>1 / 3
1 / 3

Depth is state

The deck answers how much is left without a progress bar.

CardStackControls is a separate component rather than a controls boolean: it reads the same context and can be placed under the deck, beside it, or in a toolbar. The deck is also the one place the glass material is refused — overlapping translucent surfaces show each other's text.

What it deliberately is not

Not a carousel: there is no track, no autoplay and no slide transition.

Not a modal: the deck stays in the document, so the page keeps its scroll position.

Not pointer-only: every position is reachable from the keyboard and from the controls.

Interactive data motion

SelectionBeam measures two live DOM nodes and draws a travelling packet between them. Selection, marker and inspector stop being three separate highlights and become one gesture.

<SelectionBeam from={rowRef} to={inspectorRef} active tone />

Arrow through the rows — the link follows the selection

412

APAC

EMEA

Americas

LATAM

Inspector

APAC

412M

+6.4% quarter over quarter

Nothing here is a finance calculation: the beam takes two refs and knows nothing about tables or charts.

The overlay is position: fixed, so it needs no positioned ancestor and cannot be clipped by one. Measurement is rAF-coalesced and re-runs on resize and on any scroll, captured at the window so a beam inside a scrolling pane stays attached.

The contract behind all six

Spatial motion is expensive if it is careless. These are the rules these primitives are built to, and the ones a product can rely on.

Compositor only

Transform and opacity. No animated filters, no per-frame layout reads, no React render per pointer move.

Reduced motion keeps the composition

Positions, rings and layers still resolve to a deliberate arrangement — the motion stops, the design does not disappear.

Nothing decorative carries meaning

Every effect layer is aria-hidden and pointer-events-none; state is carried by semantics, colour and text.

Luc UI v1.0Motion gallerydark theme