Components / Motion / Reorder
Reorder beta
A ranked list that re-sorts itself — rows glide to their new positions instead of jumping.
- 1Pricing reply98
- 2Renewal follow-up87
- 3Thursday demo brief76
- 4Invoice question64
Uses: Aurora, Glass Panel — include its CSS alongside this one.
<div class="k-aurora" style="max-width:430px">
<i class="b1"></i><i class="b2"></i><i class="b3"></i>
<div style="position:relative;padding:20px">
<div class="k-glass" data-theme="dark">
<div class="k-glass-h">Ranked by impact <button class="k-rank-btn" data-rank-btn type="button">Re-rank</button></div>
<ul class="k-rank" data-rank>
<li data-score="98"><span class="no">1</span>Pricing reply<span class="val">98</span></li>
<li data-score="87"><span class="no">2</span>Renewal follow-up<span class="val">87</span></li>
<li data-score="76"><span class="no">3</span>Thursday demo brief<span class="val">76</span></li>
<li data-score="64"><span class="no">4</span>Invoice question<span class="val">64</span></li>
</ul>
</div>
</div>
</div>.k-rank{list-style:none;margin:10px 0 0;padding:0;display:grid;gap:7px;font-family:var(--k-font)}
.k-rank li{display:flex;align-items:center;gap:11px;background:color-mix(in srgb,var(--k-on-color) 9%,transparent);border:1px solid color-mix(in srgb,var(--k-on-color) 18%,transparent);border-radius:10px;padding:10px 12px;font-size:13px;color:var(--k-ink)}
.k-rank .no{font-family:var(--k-mono);font-size:11px;color:var(--k-ink-3);width:14px}
.k-rank .val{margin-left:auto;font-size:11.5px;color:var(--k-ink-3)}
.k-rank-btn{font:inherit;font-size:11.5px;font-weight:550;border:1px solid color-mix(in srgb,var(--k-on-color) 25%,transparent);background:transparent;color:var(--k-ink);border-radius:99px;padding:4px 11px;cursor:pointer}
const rankList = document.querySelector('[data-rank]');
const rankBtn = document.querySelector('[data-rank-btn]');
const rankReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function rankSort() {
const items = [...rankList.children];
const first = new Map(items.map(li => [li, li.getBoundingClientRect().top]));
items.forEach(li => { li.dataset.score = Math.max(40, Math.min(99, +li.dataset.score + Math.round((Math.random() - 0.5) * 36))); });
items.sort((a, b) => +b.dataset.score - +a.dataset.score).forEach(li => rankList.appendChild(li));
[...rankList.children].forEach((li, i) => {
li.querySelector('.no').textContent = i + 1;
li.querySelector('.val').textContent = li.dataset.score;
const dy = first.get(li) - li.getBoundingClientRect().top;
if (dy && !rankReduced) li.animate([{ transform: `translateY(${dy}px)` }, { transform: 'none' }], { duration: 560, easing: 'cubic-bezier(.3,.7,.3,1)' });
});
}
rankBtn.addEventListener('click', rankSort);
let rankTimer = null;
if (!rankReduced) {
rankTimer = setInterval(rankSort, 3000);
rankList.closest('.k-glass').addEventListener('pointerenter', () => clearInterval(rankTimer));
rankList.closest('.k-glass').addEventListener('pointerleave', () => { clearInterval(rankTimer); rankTimer = setInterval(rankSort, 3000); });
}
Usage
For leaderboards, priority queues and anything whose order is the message. The FLIP technique keeps the motion honest: rows travel, they never teleport.
Accessibility
Rows keep their text and rank numbers after every move, so screen readers follow the final order. Autoplay pauses on hover and never starts under prefers-reduced-motion.
Related parts
Aurora beta
A slow wash of color behind a hero — the accent and the core, drifting under glass.
Beam border beta
A single point of light travels the edge of a card. The beam is the core color — the one accent, in motion.
Converge beta
Light streams gather into one tile — many inputs, one place where they land.
Counter beta
Numbers that count up once, when they arrive on screen — machined, not bouncy.