Skip to content

DocsGet startedHow it works

PT

Get started

How it works

One attribute says what to mount, another passes the data. State lives in ARIA and the rest is CSS.

Two attributes

data-u says which component to mount. data-u-props passes the data as JSON. The rest is regular HTML.

<div data-u="slider" data-u-props='{"min":0,"max":4,"value":2,"label":"Effort"}'></div>

CSS-only components, like button and badge, don't even need data-u: the class is enough.

Lazy mounting

unslop.js looks for data-u elements when the page loads and only mounts each one when it gets within 200px of the viewport. While it waits, the element has data-u-ready="pending". Once mounted, the attribute is empty.

  • data-u-eager mounts right away, without waiting for scroll.
  • Injected HTML after load? Call Unslop.init(container).
  • Props with invalid JSON don't break the page: the component mounts with the default props and logs a warning to the console.
const box = document.querySelector("#dashboard");
box.innerHTML = '<div data-u="period"></div>';
Unslop.init(box);

State in ARIA

What's selected, open or checked lives in the ARIA attribute, and the CSS reads that same attribute. Screen reader and visuals never disagree.

"k-com">/* the system CSS paints from the attribute, not a class */
.u-select__opt[aria-selected="true"] .u-i { opacity: 1; }
[aria-sort="descending"] .u-dt__sort .u-i { transform: rotate(180deg); }

Events

A component that changes value fires u:change (the calendar fires u:apply), with the data in detail. The event bubbles up the page.

document.querySelector('[data-u="slider"]').addEventListener("u:change", (e) => {
  console.log(e.detail.value, e.detail.text);
});

Motion

Animation only on transform and opacity, from 220 to 800ms, and nothing runs off screen. With prefers-reduced-motion, durations drop to zero and each component opens in its final state.

Type to search. Arrows pick, Enter opens.