Documents v0.9.0 · previews run the real runtime · experimental features are marked exp

Draggable card

Move an element using declarative drag events.

:on(drag)event()experimental
Live previewreal runtime

Drag the card with the mouse, a pen or a finger. Release anywhere, even outside the area. Or Tab to the card and use the arrow keys.

How it works

  • :on(drag)lines 16, 19, 24Pointer drag gestures as event rules: :on(drag) runs on every move, drag(start), drag(end) and drag(cancel) at the edges. event(x) and event(y) give positions that continue across drags.
  • event()lines 20, 21Inside a mutation of an :on() rule: one field of the current event, such as event(local-x), event(key) or event(value), read once as a typed value.

A drag gesture. :on(drag) runs on every pointer move of a drag, and event(x) / event(y) give the card’s new position: its state when the drag started plus the distance moved. A second drag continues from where the card is.

Native CSS alternative

None. HTML drag and drop (draggable) moves data, not elements, and needs JavaScript.

Limitations

  • The drag starts after 3 px of movement; a press without movement does nothing.
  • Experimental: real touch and pen hardware have not been verified.

Implementation

2 files · 35 lines
style.cssreactive
/* ---- Reactive CSS -------------------------------------------------------------------------- *//* The section owns the position, so the card and the readout can both use it. --max-x and --max-y   are how far the card can move from the centre: wide enough for a 390 px area until it is measured. */.drag { --x: state(0px); --y: state(0px); --dragging: state(false); --max-x: state(140px); --max-y: state(64px); }/* The limits follow the area's real size: it is read when the pointer or the focus enters it   (the 112 × 74 px is the card plus the area's 1 px border). */.drag .area:on(pointerenter) { --max-x: set((element(width) - 112px) / 2); --max-y: set((element(height) - 74px) / 2); }.drag .area:on(focusin) { --max-x: set((element(width) - 112px) / 2); --max-y: set((element(height) - 74px) / 2); }.drag .card {  translate: var(--x) var(--y);  touch-action: none;                 /* the browser must not take the gesture for scrolling */}.drag .card:on(drag(start)) { --dragging: set(true); }/* event(x): the position at drag start plus the distance moved, so drags continue. */.drag .card:on(drag) {  --x: set(round(clamp(0px - var(--max-x), event(x), var(--max-x)), 1px));  --y: set(round(clamp(0px - var(--max-y), event(y), var(--max-y)), 1px));}.drag .card:on(drag(end)) { --dragging: set(false); }.drag:when(--dragging) .card { cursor: grabbing; scale: 1.05; box-shadow: 0 8px 20px rgb(0 0 0 / 0.2); }/* A keyboard alternative for the same states. */.drag .card:on(keydown(ArrowRight), prevent()) { --x: set(min(var(--x) + 10px, var(--max-x))); }.drag .card:on(keydown(ArrowLeft), prevent()) { --x: set(max(var(--x) - 10px, 0px - var(--max-x))); }.drag .card:on(keydown(ArrowDown), prevent()) { --y: set(min(var(--y) + 10px, var(--max-y))); }.drag .card:on(keydown(ArrowUp), prevent()) { --y: set(max(var(--y) - 10px, 0px - var(--max-y))); }.drag .x { content: var(--x); }.drag .y { content: var(--y); }
state()Function · hover any red tokenMakes each matching element the owner of an independent state with an initial value.Reference →

Source: site/src/catalog/api/draggable-card/. style.css from its Reactive CSS divider; the layout above it and the shared api.css run but are not shown.

Related

Focus & blur

Request focus and react when an element loses it.

experimentalfocus::blur

Live values

A value that changes on its own, every two seconds. Not a clock: the API has no wall-clock time.

live()random()

Mouse tracker

A dot that follows the pointer inside an area, moved with a transform.

mouse()