Liking cljdoc? Tell your friends :D

x-proximity-list

A horizontal or vertical list of slotted items that scale up as the pointer approaches — the dock-effect, applied to any list. Sibling of x-liquid-dock without the gooey SVG filter: lighter, more general, and doesn't anchor itself to a screen edge.

Tag name: x-proximity-list

Usage

<x-proximity-list radius="140" max-scale="1.6" lift="14">
  <button aria-label="Home">&#x1F3E0;</button>
  <button aria-label="Search">&#x1F50D;</button>
  <button aria-label="Messages">&#x1F4AC;</button>
  <button aria-label="Profile">&#x1F464;</button>
</x-proximity-list>

The component places its slotted children in a flex track, listens for pointer movement over the track, and writes per-item transform: translate*( ) scale( ) plus three CSS custom properties to each child. Items return to their base size via a CSS transition when the pointer leaves.

Attributes

AttributeTypeDefaultRangeDescription
directionstringhorizontalhorizontal verticalLayout axis. Drives flex direction and which arrow keys move focus.
radiusnumber12020 – 600 (px)Influence radius. Items beyond this distance from the pointer stay at base size.
max-scalenumber1.51.0 – 3.0Scale factor for the item directly under the pointer.
liftnumber00 – 60 (px)Maximum cross-axis translation toward the pointer. 0 means scale-only.
gapnumber80 – 64 (px)Gap between items (also exposed as --x-proximity-list-gap).
disabledbooleanabsentpresenceDisables proximity tracking and select events. Items render at base size.

Properties

PropertyTypeReflects attribute
directionstringdirection
radiusnumberradius
maxScalenumbermax-scale
liftnumberlift
gapnumbergap
disabledbooleandisabled

No methods.

Events

EventBubblesComposedCancelableDetail
x-proximity-list-selectyesyesyes{ index: number, item: Element, source: "pointer" \| "keyboard" }

Dispatched on click and on Enter / Space once an item is focused via arrow keys. Same shape as x-liquid-dock-select.

Slots

SlotDescription
(default slot)Items are user-supplied light-DOM children. Each direct child is reactive.

Parts

PartDescription
trackFlex container that holds the slot.

Theming — CSS Custom Properties

PropertyDefaultDescription
--x-proximity-list-gap8pxGap between items.
--x-proximity-list-item-size48pxDefault width/height for items.

Per-item runtime variables

Each slotted child receives three inline-style variables on every animation frame while the pointer is over the track. They're useful if you want to drive richer per-item visuals (shadow, glow, label opacity) than the default scale + translate.

PropertyRangeDescription
--x-proximity-influence01Linear distance falloff from the pointer.
--x-proximity-scale1max-scaleCurrent scale factor applied to the item.
--x-proximity-lift-lift+lift (px)Current cross-axis translation.

Example — style your item to glow proportionally to influence:

::slotted(*) {
  box-shadow: 0 0 calc(var(--x-proximity-influence, 0) * 24px)
              rgba(99, 102, 241, calc(var(--x-proximity-influence, 0) * 0.6));
}

Accessibility

  • The host element gets role="list" automatically if no role was set.
  • Slotted children that don't already have tabindex receive tabindex="0" on slotchange.
  • Arrow keys move focus among items (Left/Right for horizontal, Up/Down for vertical).
  • Enter or Space on a focused item dispatches x-proximity-list-select with source: "keyboard".
  • The proximity effect is purely visual — focus, click, and keyboard activation are never gated by it.

Mobile and reduced motion

  • Touch devices: proximity tracking is suppressed (only pointerType === "mouse" drives the effect). Click and keyboard select still work, so the list stays fully functional without the visual dock effect.
  • prefers-reduced-motion: reduce: the RAF loop is skipped, items stay at base size, and CSS transitions are disabled. Click and keyboard select still work.

How it works

The pipeline is stateless — render! = f(attributes, pointer, displayed):

  1. pointermove over the track caches (x, y) in track-relative coordinates and schedules a requestAnimationFrame (only one in flight at a time).
  2. On each frame, every item's centre is compared to the pointer using a linear distance falloff (compute-influence); the influence drives a quadratic ramp on scale (compute-scale) and an optional cross-axis lift (compute-lift).
  3. Each item carries a small "displayed" record (scale, lift) that lerps toward the freshly computed targets. This keeps motion smooth even when the closest item's target changes by large per-pixel increments — which is where pure CSS transitions, when reset by user CSS, make the closest item appear to "snap" while peripheral items look smooth.
  4. Three CSS variables (--x-proximity-influence, --x-proximity-scale, --x-proximity-lift) and the transform are written to each item's inline style.
  5. On pointerleave, the targets become the resting values and the lerp settles items back to base. Once every item has settled, RAF stops and inline styles are cleared so the user's CSS takes over again cleanly.
  6. A ResizeObserver re-caches per-item rects when the host or items resize.

No mutable application state, no signals, no virtual DOM — just f(attributes, pointer, displayed) → inline styles. The component is robust against author CSS that resets the transition shorthand on slotted items, because all smoothing happens in JS.

Examples

Horizontal default

<x-proximity-list>
  <button>A</button>
  <button>B</button>
  <button>C</button>
  <button>D</button>
</x-proximity-list>

Vertical sidebar with lift

<x-proximity-list direction="vertical" lift="20" max-scale="1.8">
  <a href="#home">Home</a>
  <a href="#search">Search</a>
  <a href="#profile">Profile</a>
</x-proximity-list>

Wider influence, subtler scale

<x-proximity-list radius="240" max-scale="1.2">
  <img src="..." />
  <img src="..." />
  <img src="..." />
</x-proximity-list>

Listening for selection

const list = document.querySelector('x-proximity-list');
list.addEventListener('x-proximity-list-select', (e) => {
  console.log('selected', e.detail.index, e.detail.source);
});

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close