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-eagermounts 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.