Skip to content

Motion

Signature motion

Five new interaction primitives, plus a stronger default for the controls you already use. Everything here is a public @luc/ui export driven by the resolved intensity profile, so a product can scope it down to functional — or off — without touching a call site.

Intensity is a product decision, not a component clamp

MotionIntensityProvider scopes how loud everything below it is allowed to be. Reduced motion still wins over every level. Change it and watch the whole page respond.

`none` renders static output, `functional` keeps feedback but drops cursor effects, `expressive` is the house default, `showcase` is for the one surface that deserves to be remembered.

PressRipple

Tactile ink under the pointer, derived from where the finger actually landed. The Button and IconButton families use it by default; anything else can adopt it through the hook or the wrapper.

Press ink

usePressRipple() · <RippleLayer /> · <PressRipple>
Press anywhere in this panel — 0 presses

Two rings per press on a filled control, one on a quiet one. Never on hover, never on focus, gone entirely under reduced motion.

AuroraSurface

A living background for the one moment that should be remembered: a hero, an auth screen, an empty state. The light is derived from the live accent, so switching palette re-tints the whole atmosphere.

Ambient light, derived

<AuroraSurface tone density speed grid vignette />
AuroraSurface

A background that reacts to the brand, not to a hardcoded gradient.

Nothing above the light layer is dimmed, tinted or filtered — the fields are mixed from the accent token and blurred behind the content.

Compositor-only drift on a ≥14s loop, masked and vignetted so foreground text keeps full contrast. Reduced motion keeps the composition and drops the movement.

BeamFlow

A connector that shows something moving between two places: an AI request from model to tool to answer, an ingest pipeline, an onboarding map. It is an overlay, so the nodes stay ordinary LUC surfaces.

Something is happening here

<BeamFlow points state width speed label />

Retrieve

Reason

Answer

One SVG in a 0-100 viewBox with non-scaling strokes, so the connector is responsive with no ResizeObserver. The rail stays when the packet stops.

MagnifyDock

A toolbar that answers the most basic question a dense toolbar raises: which of these am I about to hit? Pointer proximity swells each item along a cosine falloff.

Dock physics

<MagnifyDock range scale lift orientation> · <MagnifyDockItem>

Hover across the dock. Every item is a real IconButton with its own focus ring.

Pointer movement never re-renders React: the container writes two custom properties onto cached element references inside one animation frame.

None yet

Marquee

A strip that never stops, reserved for content that is genuinely a stream: a logo wall, a live ticker, a long list of names. Pause on hover is on by default, because an unreadable strip is a usability defect.

A stream, not a layout trick

<Marquee speed direction gap pauseOnHover fade>
Design tokensData tablesChartsWorkspacesMotionFormsAccessibilityBilingualDark & light

Two copies of the track, the second hidden from assistive technology, each shifted by exactly its own width — so the loop is seamless at any content length.

And the primitives that were already here

The same motion contract governs the older layer. These are unchanged in API and were re-verified in the same pass — Tile, BorderGlow, SpotlightSurface, Reveal and the fluid indicator all read the intensity profile above.

TiltSurface + BorderGlow

Pointer depth

Lean, light and edge highlight from one pointer position.

Text that arrives
Shimmering headline

A moving highlight in the glyph fill, never a layer over it.

Motion that confirms an outcome

IconMorph swaps one glyph for the next in place; ClickBurst punctuates a completion. Both are state-triggered, so they answer something the user did rather than animating on a timer.

A state change is the only trigger: the label and the icon swap together, the fill moves to the accent variant, and nothing loops afterwards.

Accessibility contract

Everything on this page is covered by the same two gates as the rest of the system.

Reduced motion wins

The global clamp in base.css plus the JS profile mean a decorative effect cannot run when the user asks for less. One-shot effects simply never spawn.

Nothing carries meaning

Every layer is aria-hidden and pointer-events-none. A selection, an error or a status is always carried by semantics and colour, never by an animation.

No motion on content you read

The aurora and the marquee are the only looping effects, both slow, both optional per surface, and both stopped by hover where reading is possible.

On this page: PressRipple · AuroraSurface · BeamFlow · MagnifyDock · Marquee

Luc UI v1.0Motion gallerydark theme