Wheel

<o-wheel> is a prize wheel: it spins, slows down and lands on a result picked by weight.

The segments are <o-arc>s with a value, so Orbit stacks them around one ring, each as wide as its weight. The turning part is a universe inside the universe: a nested .bigbang that the Web Animations API rotates, so Orbit has nothing to redraw during a spin. The pointer and the button stay in the outer system. Each label is a satellite on its own ring, with --o-from at the middle of its segment.

The result is picked first, fairly by weight with crypto.getRandomValues, and the wheel then turns a few times and slows down onto it with a long ease-out. Every time a peg passes, the pointer flicks and a tick event fires. When it stops, labels on the left half turn over to read upright.

Attributes

AttributeDefaultWhat it does
items[]JSON: strings, or { label, color?, weight?, value? }. weight (default 1) sets both the size of the segment and its odds; 0 leaves an item out. Up to 100 items.
labelsacrossacross writes labels from the middle out, along writes them along the rim, none hides them. Long labels are shortened with an ellipsis.
duration5Length of a spin, in seconds (4, 4s or 4000ms).
turns5Full turns before the wheel settles.
remove-winneroffFor raffles: the winner stays highlighted, then leaves the wheel when the next spin starts.
controlsoncontrols="false" hides the Spin button. The wheel itself then becomes the button.
labelSpinText of the button.
pegsup to 36 itemsDots on the rim at each boundary. pegs="false" hides them.
gap0.4Space between segments, in percent of the widget width.
disabledoffNo spins from the button, the pointer, keys or spin().
size300pxWidth of the widget.
themedark, light or glass. Also inherited from an ok-theme-* class on a container. See Shared options.

Without a color, items take the six accents of the demos in turn, never the same color twice in a row. Label text is dark or white, whichever reads better on its segment.

Properties, methods and events

NameWhat it does
spin(index?)Spins and returns a promise with the result. Without an index the result is random, weighted; with one, the wheel lands on that item. While spinning it returns the same promise. It resolves with null when the wheel is empty or disabled.
itemsThe items, as given. Setting it writes the attribute.
spinningtrue during a spin.
resultThe last result, or null.
spinA spin started. event.detail.duration is its length in seconds.
tickA boundary passed the pointer. event.detail has the index and label now under it. Good for a click sound.
resultThe wheel stopped. event.detail has index, label, value and item.
wheel.addEventListener('result', (e) => claim(e.detail.value))
const { label } = await wheel.spin()

Styling

PropertyDefaultWhat it does
--ok-wheel-rim--ok-bezel, #232b35Rim, lines between segments and the button's ring.
--ok-wheel-pointer--ok-hand, #e8edf2The pointer.
--ok-wheel-peg--ok-hand, #e8edf2Pegs on the rim.
--ok-wheel-hub--ok-surface, #161b22The button.
--ok-wheel-hub-text--ok-text, #e8edf2Text of the button.
--ok-focus#3da9fcFocus ring.
--ok-font-size8cqminBase font size of the button, as a share of the widget.

During a spin the element has the class is-spinning; after one, has-result, and the winning segment is-winner while the others fade.

More examples

Labels along the rim, and weights

The jackpot is half as wide as the others, so it comes up half as often.

A raffle

With remove-winner every name can win once. Setting items again starts over.

A result decided elsewhere

When the outcome comes from a server, ask it first and pass the index to spin(). Here the buttons stand in for the server, and the wheel is the button itself.

Accessibility

The wheel is a role="group" named after its items, with a real button in the middle. Enter or Space on the button spins it. With controls="false" the wheel itself is a role="button" in the tab order, and Enter or Space spin it. During a spin the button is aria-disabled and the wheel aria-busy; the result is announced through a polite live region, as “Result: Pizza”.

With reduced motion the spin takes under a second, without extra turns or the flicking pointer. The result event and the announcement are the same.