Get started

Install loopem and mount your first picker.

Loopem has no dependencies. One package covers every framework, and the adapters are subpaths of it.

$ npm install loopem

Your first picker

Give it an element, a list, and a way to build one item. Everything else has a default.

The element is one empty div. You never write the items into your markup: loopem creates them, and keeps only the ones on screen.

index.htmlhtml
<div id="colors"></div>

The items can be anything. Loopem never looks inside one, and renderItem decides what it looks like.

picker.jsts
import { createPicker } from 'loopem'

const colors = [
  { name: 'Ember', hex: '#e8574a' },
  { name: 'Amber', hex: '#e0a33a' },
  { name: 'Fern', hex: '#7cc44f' },
  // …twenty in all
]

const picker = createPicker(document.getElementById('colors'), {
  items: colors,
  renderItem: (color) => {
    const button = document.createElement('button')
    button.style.background = color.hex
    button.setAttribute('aria-label', color.name)
    return button
  },
  fade: 100,
})

fade dissolves the last 100px at each end, so the track runs out of the frame instead of stopping at a hard edge. Everything else here is a default.

That code, running. Drag it, flick it, scroll across it, or focus it and use the arrow keys.

That is a working picker. It drags, flicks with momentum, never rests between two items, answers to the keyboard, and announces itself to a screen reader.

Sizing it

Loopem positions items; it never sizes them. The container needs a height, and each item needs whatever size you want it to be. Both are ordinary CSS.

styles.csscss
#colors {
  height: 240px;
}

#colors button {
  width: 56px;
  height: 56px;
  border: 0;
  border-radius: 20px;
}

Reading what was picked

Nothing happens in your app until you ask.

ts
picker.on('select', ({ index, item }) => {
  console.log('Picked', item, 'at', index)
}, { immediate: true })

immediate: true also runs the listener once on subscribe, so one line fills your view and keeps it up to date. select fires when the track settles, so it is where anything expensive belongs. There is a full account in Read the selection.

Cleaning up

destroy() removes the listeners, the slots, and the rAF loop. In a framework, the adapters do it for you.

ts
picker.destroy()

Where to go next

  • Options lists every option, with a picker you can change them on.
  • Line & arc covers the two built-in shapes.
  • React, Vue and Svelte each have a first-party adapter.