Open demo

CSS code

HTML
<div><span class="lbl">Nested outline: sections and pages</span>
<section class="tr" data-dd aria-labelledby="tr-title">
  <div class="tr-head"><h2 id="tr-title">Docs navigation</h2><p id="tr-hint">Drag sections or pages. With the keyboard: focus a handle, press Space, use Up and Down; Left and Right move a page to the previous or next section.</p></div>
  <ul class="tr-sections" data-dd-list="sections" aria-label="Sections">
    <li class="tr-sec" data-dd-item><div class="tr-row"><button class="tr-handle" type="button" data-dd-handle aria-describedby="tr-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2 5.5A1.5 1.5 0 0 1 3.5 4h4l2 2h7A1.5 1.5 0 0 1 18 7.5v7a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 2 14.5z"/></svg><span class="tr-name" data-dd-name>Getting started</span><span class="tr-n"><span data-dd-count="tr-start">3</span> pages</span></div>
      <ul class="tr-pages" id="tr-start" data-dd-list="pages" aria-label="Getting started">
        <li class="tr-page" data-dd-item><div class="tr-row"><button class="tr-handle" type="button" data-dd-handle aria-describedby="tr-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="tr-name" data-dd-name>Installation</span></div></li>
        <li class="tr-page" data-dd-item><div class="tr-row"><button class="tr-handle" type="button" data-dd-handle aria-describedby="tr-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="tr-name" data-dd-name>Quick start</span></div></li>
        <li class="tr-page" data-dd-item><div class="tr-row"><button class="tr-handle" type="button" data-dd-handle aria-describedby="tr-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="tr-name" data-dd-name>Project structure</span></div></li>
      </ul></li>
    <li class="tr-sec" data-dd-item><div class="tr-row"><button class="tr-handle" type="button" data-dd-handle aria-describedby="tr-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2 5.5A1.5 1.5 0 0 1 3.5 4h4l2 2h7A1.5 1.5 0 0 1 18 7.5v7a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 2 14.5z"/></svg><span class="tr-name" data-dd-name>Guides</span><span class="tr-n"><span data-dd-count="tr-guides">3</span> pages</span></div>
      <ul class="tr-pages" id="tr-guides" data-dd-list="pages" aria-label="Guides">
        <li class="tr-page" data-dd-item><div class="tr-row"><button class="tr-handle" type="button" data-dd-handle aria-describedby="tr-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="tr-name" data-dd-name>Theming</span></div></li>
        <li class="tr-page" data-dd-item><div class="tr-row"><button class="tr-handle" type="button" data-dd-handle aria-describedby="tr-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="tr-name" data-dd-name>Forms and validation</span></div></li>
        <li class="tr-page" data-dd-item><div class="tr-row"><button class="tr-handle" type="button" data-dd-handle aria-describedby="tr-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="tr-name" data-dd-name>Deploying</span></div></li>
      </ul></li>
    <li class="tr-sec" data-dd-item><div class="tr-row"><button class="tr-handle" type="button" data-dd-handle aria-describedby="tr-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2 5.5A1.5 1.5 0 0 1 3.5 4h4l2 2h7A1.5 1.5 0 0 1 18 7.5v7a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 2 14.5z"/></svg><span class="tr-name" data-dd-name>Reference</span><span class="tr-n"><span data-dd-count="tr-ref">2</span> pages</span></div>
      <ul class="tr-pages" id="tr-ref" data-dd-list="pages" aria-label="Reference">
        <li class="tr-page" data-dd-item><div class="tr-row"><button class="tr-handle" type="button" data-dd-handle aria-describedby="tr-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="tr-name" data-dd-name>CLI commands</span></div></li>
        <li class="tr-page" data-dd-item><div class="tr-row"><button class="tr-handle" type="button" data-dd-handle aria-describedby="tr-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="tr-name" data-dd-name>Configuration file</span></div></li>
      </ul></li>
  </ul>
  <p class="sr-only" aria-live="assertive" aria-atomic="true" data-dd-live></p>
</section></div>

<div><span class="lbl">Priority ranking with numbers</span>
<section class="tr" data-dd data-ranking aria-labelledby="rk-title">
  <div class="tr-head"><h2 id="rk-title">What matters most in your next laptop?</h2><p id="rk-hint">Drag a row, use the arrow buttons, or focus a handle, press Space and use Up and Down.</p></div>
  <ol class="rk-list" data-dd-list="ranks">
    <li class="rk" data-dd-item><button class="tr-handle" type="button" data-dd-handle aria-describedby="rk-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="rk-no" data-rank aria-hidden="true">1</span><span class="rk-text"><b data-dd-name>Battery life</b><small>All day away from a charger</small></span><span class="rk-steps"><button class="rk-step" type="button" data-step="-1" aria-label="Move Battery life up"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="rk-step" type="button" data-step="1" aria-label="Move Battery life down"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="rk" data-dd-item><button class="tr-handle" type="button" data-dd-handle aria-describedby="rk-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="rk-no" data-rank aria-hidden="true">2</span><span class="rk-text"><b data-dd-name>Weight</b><small>Under 1.3 kg to carry every day</small></span><span class="rk-steps"><button class="rk-step" type="button" data-step="-1" aria-label="Move Weight up"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="rk-step" type="button" data-step="1" aria-label="Move Weight down"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="rk" data-dd-item><button class="tr-handle" type="button" data-dd-handle aria-describedby="rk-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="rk-no" data-rank aria-hidden="true">3</span><span class="rk-text"><b data-dd-name>Screen</b><small>Bright and colour-accurate</small></span><span class="rk-steps"><button class="rk-step" type="button" data-step="-1" aria-label="Move Screen up"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="rk-step" type="button" data-step="1" aria-label="Move Screen down"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="rk" data-dd-item><button class="tr-handle" type="button" data-dd-handle aria-describedby="rk-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="rk-no" data-rank aria-hidden="true">4</span><span class="rk-text"><b data-dd-name>Keyboard</b><small>Comfortable for long writing sessions</small></span><span class="rk-steps"><button class="rk-step" type="button" data-step="-1" aria-label="Move Keyboard up"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="rk-step" type="button" data-step="1" aria-label="Move Keyboard down"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="rk" data-dd-item><button class="tr-handle" type="button" data-dd-handle aria-describedby="rk-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="rk-no" data-rank aria-hidden="true">5</span><span class="rk-text"><b data-dd-name>Price</b><small>Stays under the budget</small></span><span class="rk-steps"><button class="rk-step" type="button" data-step="-1" aria-label="Move Price up"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="rk-step" type="button" data-step="1" aria-label="Move Price down"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="rk" data-dd-item><button class="tr-handle" type="button" data-dd-handle aria-describedby="rk-hint"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="rk-no" data-rank aria-hidden="true">6</span><span class="rk-text"><b data-dd-name>Ports</b><small>USB-C, HDMI and a card reader</small></span><span class="rk-steps"><button class="rk-step" type="button" data-step="-1" aria-label="Move Ports up"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="rk-step" type="button" data-step="1" aria-label="Move Ports down"><svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
  </ol>
  <p class="sr-only" aria-live="assertive" aria-atomic="true" data-dd-live></p>
</section></div>
CSS
/* Nested reorder and ranking. Sections and pages are separate groups (data-dd-list="sections" / "pages"), so a section
   only lands between sections and a page only lands in a page list, at any depth. Ranks are renumbered on every move. */
.tr{--tr-accent:#2563eb;--tr-tint:#eaf1ff;--tr-line:#e4e7ec;--tr-muted:#5d6679;--tr-ink:#101828;
  background:#fff;border:1px solid var(--tr-line);border-radius:16px;padding:16px 16px 18px;color:var(--tr-ink);box-shadow:0 1px 2px rgba(16,24,40,.05),0 10px 28px -16px rgba(16,24,40,.18)}
.tr-head{display:grid;gap:2px;margin:2px 4px 12px}
.tr-head h2{font-size:1rem;letter-spacing:-.01em}
.tr-head p{font-size:.82rem;color:var(--tr-muted)}
.tr-sections,.tr-pages,.rk-list{list-style:none}
.tr-sections{display:grid;gap:6px}
.tr-sec{border-radius:12px;padding:2px 0;cursor:grab;user-select:none}
.tr-row{display:flex;align-items:center;gap:8px;padding:6px 8px 6px 4px;border-radius:10px;background:#fff}
.tr-row svg.tr-ico{flex:none;width:18px;height:18px}
.tr-sec>.tr-row{font-size:.9rem;font-weight:700}
.tr-sec>.tr-row .tr-ico{color:var(--tr-accent)}
.tr-name{flex:1;min-width:0}
.tr-n{font-size:.72rem;font-weight:700;color:var(--tr-muted)}
.tr-pages{display:grid;gap:2px;margin:2px 0 4px 21px;padding:2px 0 2px 14px;border-left:2px solid var(--tr-line);min-height:38px;border-radius:0 10px 10px 0;transition:background-color .15s}
.tr-pages[data-empty]::after{content:"Drop pages here";display:grid;place-items:center;height:34px;border:2px dashed #b3bbc8;border-radius:8px;font-size:.78rem;color:var(--tr-muted)}
.tr-pages[data-over]{background:var(--tr-tint);border-left-color:var(--tr-accent)}
.tr-page{border-radius:10px;cursor:grab}
.tr-page .tr-row{font-size:.86rem;font-weight:500}
.tr-page .tr-ico{color:#667085}
.tr-handle{flex:none;display:grid;place-items:center;width:28px;height:30px;border:0;border-radius:8px;background:none;color:#667085;cursor:grab;touch-action:none}
.tr-handle:hover{background:#f2f4f7;color:var(--tr-ink)}
.tr-handle:focus-visible{outline:2px solid var(--tr-accent);outline-offset:1px}
.tr-handle svg{width:16px;height:16px}
.rk-list{display:grid;gap:8px;counter-reset:none}
.rk{display:flex;align-items:center;gap:12px;padding:10px 10px 10px 8px;border:1px solid var(--tr-line);border-radius:12px;background:#fff;cursor:grab;user-select:none}
.rk-no{flex:none;display:grid;place-items:center;width:32px;height:32px;border-radius:10px;background:#f2f4f7;color:#344054;font-weight:800;font-size:.9rem;font-variant-numeric:tabular-nums}
.rk:nth-child(-n+3) .rk-no{background:var(--tr-accent);color:#fff}
.rk-text{flex:1;min-width:0;display:grid}
.rk-text b{font-size:.92rem;font-weight:600}
.rk-text small{font-size:.78rem;color:var(--tr-muted)}
.rk-steps{display:flex;gap:4px}
.rk-step{display:grid;place-items:center;width:30px;height:30px;border:1px solid #d0d5dd;border-radius:8px;background:#fff;color:#344054;cursor:pointer}
.rk-step:hover{background:#f2f4f7}
.rk-step:focus-visible{outline:2px solid var(--tr-accent);outline-offset:1px}
.rk-step[aria-disabled="true"]{color:#98a2b3;background:#f9fafb;cursor:default}
.rk-step svg{width:16px;height:16px}
/* states set by the script */
.tr-sec[data-dragging],.tr-page[data-dragging],.rk[data-dragging]{background:var(--tr-tint);outline:2px dashed #93b4f8;outline-offset:-2px}
.tr-sec[data-dragging]>*,.tr-page[data-dragging]>*,.rk[data-dragging]>*{visibility:hidden}
.tr-sec[data-dd-ghost],.tr-page[data-dd-ghost]{background:#fff;box-shadow:0 16px 36px -12px rgba(16,24,40,.4);rotate:1deg}
.rk[data-dd-ghost]{box-shadow:0 16px 36px -12px rgba(16,24,40,.4);rotate:1deg}
.tr-sec[data-grabbed]>.tr-row,.tr-page[data-grabbed]>.tr-row,.rk[data-grabbed]{background:var(--tr-tint);box-shadow:0 0 0 2px var(--tr-accent)}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
@media (max-width:480px){.rk-text small{display:none}}
@media (prefers-reduced-motion:reduce){.tr-pages{transition:none}.tr-sec[data-dd-ghost],.tr-page[data-dd-ghost],.rk[data-dd-ghost]{rotate:none}}
JavaScript
// Ranking: renumber the ranks after every move, disable the arrow at either end, and let the
// Move up / Move down buttons reorder a row in one click (announced through the same live region).
document.addEventListener('dd:change', e => {
  const root = e.target; if (!root.matches('[data-ranking]')) return;
  const rows = [...root.querySelectorAll('[data-dd-item]')];
  rows.forEach((li, i) => {
    li.querySelector('[data-rank]').textContent = i + 1;
    li.querySelector('[data-step="-1"]').setAttribute('aria-disabled', i === 0);
    li.querySelector('[data-step="1"]').setAttribute('aria-disabled', i === rows.length - 1);
  });
});
document.addEventListener('click', e => {
  const b = e.target.closest('[data-step]'); if (!b || b.getAttribute('aria-disabled') === 'true') return;
  const li = b.closest('[data-dd-item]'), root = li.closest('[data-dd]'), up = b.dataset.step === '-1';
  const sib = up ? li.previousElementSibling : li.nextElementSibling; if (!sib) return;
  up ? sib.before(li) : sib.after(li); b.focus();
  root.dispatchEvent(new Event('dd:change', { bubbles: true }));
  const live = root.querySelector('[data-dd-live]'), n = [...li.parentElement.children].indexOf(li) + 1;
  live.textContent = li.querySelector('[data-dd-name]').textContent + ' moved to rank ' + n + ' of ' + li.parentElement.children.length + '.';
});
// Drag and drop with pointer events (mouse, touch and pen) plus a keyboard mode on every handle:
// Space or Enter picks an item up, the arrow keys move it, Space or Enter drops it, Escape puts it back.
// Every move is announced in the component's aria-live region. Markup contract: [data-dd] root,
// [data-dd-list="group"] lists, [data-dd-item] items with a [data-dd-name] and a [data-dd-handle] button.
// data-dd="grid" or "wrap" compares the pointer horizontally; in a grid, Up and Down jump a whole row.
document.querySelectorAll('[data-dd]').forEach(root => {
  const grid = root.dataset.dd === 'grid', horiz = grid || root.dataset.dd === 'wrap', live = root.querySelector('[data-dd-live]');
  const items = l => [...l.children].filter(c => c.hasAttribute('data-dd-item'));
  const lists = l => [...root.querySelectorAll(`[data-dd-list="${l.dataset.ddList}"]`)];
  const name = el => el.querySelector('[data-dd-name]').textContent.trim();
  const full = l => !!l.dataset.ddMax && items(l).length >= +l.dataset.ddMax;
  const pos = el => { const l = el.parentElement, all = items(l);
    return (lists(l).length > 1 ? l.getAttribute('aria-label') + ', ' : '') + 'position ' + (all.indexOf(el) + 1) + ' of ' + all.length; };
  const say = msg => { live.textContent = ''; setTimeout(() => { live.textContent = msg; }, 60); };
  const clearOver = keep => root.querySelectorAll('[data-over]').forEach(l => l !== keep && l.removeAttribute('data-over'));
  const marks = () => root.querySelectorAll('[data-dd-list]').forEach(l => { const n = items(l).length, c = root.querySelector(`[data-dd-count="${l.id}"]`);
    l.toggleAttribute('data-empty', !n); l.toggleAttribute('data-full', full(l)); if (c) c.textContent = n; });
  const changed = () => {
    marks();
    root.querySelectorAll('[data-dd-item]').forEach(it => it.querySelector('[data-dd-handle]').setAttribute('aria-label', 'Move ' + name(it)));
    root.dispatchEvent(new Event('dd:change', { bubbles: true }));
  };
  let drag, ghost, home, kb, kbh, sx, sy, ox, oy, pid, dragged, moving;
  const putBack = el => home[0].insertBefore(el, home[1] && home[1].parentNode === home[0] ? home[1] : null);

  root.addEventListener('pointerdown', e => {
    const item = e.target.closest('[data-dd-item]'), handle = e.target.closest('[data-dd-handle]');
    if (!item || e.button > 0 || kb) return;
    if (!handle && (e.pointerType !== 'mouse' || 'ddHandleOnly' in root.dataset || e.target.closest('a,button,input,select,textarea,label'))) return;
    e.preventDefault(); drag = item; pid = e.pointerId; sx = e.clientX; sy = e.clientY; ghost = null;
  });
  root.addEventListener('pointermove', e => {
    if (!drag || e.pointerId !== pid) return;
    if (!ghost) { if (Math.hypot(e.clientX - sx, e.clientY - sy) < 6) return; start(); root.setPointerCapture(pid); }
    ghost.style.translate = `${e.clientX - ox}px ${e.clientY - oy}px`;
    if (e.clientY < 40) scrollBy(0, -12); else if (e.clientY > innerHeight - 40) scrollBy(0, 12);
    const hit = document.elementFromPoint(e.clientX, e.clientY), sel = `[data-dd-list="${home[0].dataset.ddList}"]`;
    const list = hit && (hit.closest(sel) || hit.closest('[data-dd-zone]')?.querySelector(sel)); // a [data-dd-zone] wrapper widens the target
    clearOver(list);
    if (!list || !root.contains(list) || drag.contains(list)) return;
    list.setAttribute('data-over', '');
    if (list !== drag.parentElement && full(list)) return;
    const over = items(list).find(c => c !== drag && c.contains(hit));
    if (over) { const r = over.getBoundingClientRect();
      const ref = (horiz ? e.clientX > r.left + r.width / 2 : e.clientY > r.top + r.height / 2) ? over.nextSibling : over;
      if (ref !== drag && ref !== drag.nextSibling) { list.insertBefore(drag, ref); marks(); }
    } else if (list !== drag.parentElement && !drag.contains(hit)) { list.append(drag); marks(); }
  });
  const end = e => {
    if (!drag || e.pointerId !== pid) return;
    if (ghost) {
      ghost.remove(); drag.removeAttribute('data-dragging'); root.removeAttribute('data-dd-active'); document.body.style.cursor = ''; clearOver();
      if (e.type === 'pointercancel') putBack(drag); else say(name(drag) + ' moved to ' + pos(drag) + '.');
      changed(); dragged = true; setTimeout(() => { dragged = false; });
    }
    drag = null;
  };
  root.addEventListener('pointerup', end); root.addEventListener('pointercancel', end);
  function start() {
    const r = drag.getBoundingClientRect(); ox = sx - r.left; oy = sy - r.top; home = [drag.parentElement, drag.nextSibling];
    ghost = drag.cloneNode(true); ghost.removeAttribute('data-dd-item'); ghost.setAttribute('data-dd-ghost', ''); ghost.setAttribute('aria-hidden', 'true'); ghost.inert = true;
    ghost.querySelectorAll('[id]').forEach(n => n.removeAttribute('id'));
    Object.assign(ghost.style, { position: 'fixed', left: 0, top: 0, width: r.width + 'px', height: r.height + 'px', margin: 0, zIndex: 999, pointerEvents: 'none' });
    drag.after(ghost); drag.setAttribute('data-dragging', ''); root.setAttribute('data-dd-active', ''); document.body.style.cursor = 'grabbing';
  }

  // Keyboard and screen reader mode: the handle is a toggle button (aria-pressed) that picks the item up.
  const finish = msg => { kb.removeAttribute('data-grabbed'); kbh.setAttribute('aria-pressed', 'false'); root.removeAttribute('data-dd-active'); clearOver(); kb = null; changed(); say(msg); };
  const move = fn => { moving = true; fn(); kbh.focus(); moving = false; kbh.scrollIntoView({ block: 'nearest' }); clearOver(kb.parentElement); kb.parentElement.setAttribute('data-over', ''); changed(); say(name(kb) + ' moved to ' + pos(kb) + '.'); };
  root.addEventListener('click', e => {
    const h = e.target.closest('[data-dd-handle]'); if (!h || dragged) return;
    const item = h.closest('[data-dd-item]');
    if (kb === item) return finish(name(item) + ' dropped, ' + pos(item) + '.');
    if (kb) finish(name(kb) + ' dropped, ' + pos(kb) + '.');
    kb = item; kbh = h; home = [item.parentElement, item.nextSibling];
    item.setAttribute('data-grabbed', ''); h.setAttribute('aria-pressed', 'true'); root.setAttribute('data-dd-active', ''); item.parentElement.setAttribute('data-over', ''); h.focus();
    say(name(item) + ' picked up, ' + pos(item) + '. Use the arrow keys to move it, Space to drop it, Escape to cancel.');
  });
  root.addEventListener('keydown', e => {
    if (!kb || e.target !== kbh) return;
    if (e.key === 'Escape') { e.preventDefault(); moving = true; putBack(kb); kbh.focus(); moving = false; return finish('Move cancelled. ' + name(kb) + ' returned to ' + pos(kb) + '.'); }
    const d = { ArrowUp: -1, ArrowLeft: -1, ArrowDown: 1, ArrowRight: 1 }[e.key]; if (!d) return;
    e.preventDefault();
    const l = kb.parentElement, all = items(l), i = all.indexOf(kb), ls = lists(l), side = e.key === 'ArrowLeft' || e.key === 'ArrowRight';
    if (side && ls.length > 1) {
      const t = ls[ls.indexOf(l) + d];
      if (!t) return say(name(kb) + ' is already in ' + l.getAttribute('aria-label') + ', the ' + (d < 0 ? 'first' : 'last') + ' list.');
      if (full(t)) return say(t.getAttribute('aria-label') + ' is full.');
      return move(() => t.insertBefore(kb, items(t)[i] || null));
    }
    const step = grid && !side ? d * all.filter(x => x.offsetTop === all[0].offsetTop).length : d, j = Math.max(0, Math.min(all.length - 1, i + step));
    if (j === i) return say(name(kb) + ' is already ' + (d < 0 ? 'first' : 'last') + '.');
    move(() => l.insertBefore(kb, j > i ? all[j].nextSibling : all[j]));
  });
  root.addEventListener('focusout', e => { if (kb && !moving && e.target === kbh) finish(name(kb) + ' dropped, ' + pos(kb) + '.'); });
  changed();
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Nested outline: sections and pages</span>
<section class="tr card" data-dd aria-labelledby="tr-title">
  <p class="visually-hidden" aria-live="assertive" aria-atomic="true" data-dd-live></p>
  <div class="tr-head d-grid gap-1 mx-1 mb-3"><h2 class="fw-bold mb-0" id="tr-title">Docs navigation</h2><p class="text-body-secondary mb-0" id="tr-hint">Drag sections or pages. With the keyboard: focus a handle, press Space, use Up and Down; Left and Right move a page to the previous or next section.</p></div>
  <ul class="d-grid gap-2 list-unstyled mb-0" data-dd-list="sections" aria-label="Sections">
    <li class="tr-sec" data-dd-item><div class="tr-row d-flex align-items-center gap-2 fw-bold"><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2 5.5A1.5 1.5 0 0 1 3.5 4h4l2 2h7A1.5 1.5 0 0 1 18 7.5v7a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 2 14.5z"/></svg><span class="flex-grow-1" data-dd-name>Getting started</span><span class="tr-n fw-bold text-body-secondary"><span data-dd-count="tr-start">3</span> pages</span></div>
      <ul class="tr-pages d-grid gap-1 list-unstyled" id="tr-start" data-dd-list="pages" aria-label="Getting started">
        <li class="tr-page" data-dd-item><div class="tr-row d-flex align-items-center gap-2 fw-medium"><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="flex-grow-1" data-dd-name>Installation</span></div></li>
        <li class="tr-page" data-dd-item><div class="tr-row d-flex align-items-center gap-2 fw-medium"><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="flex-grow-1" data-dd-name>Quick start</span></div></li>
        <li class="tr-page" data-dd-item><div class="tr-row d-flex align-items-center gap-2 fw-medium"><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="flex-grow-1" data-dd-name>Project structure</span></div></li>
      </ul></li>
    <li class="tr-sec" data-dd-item><div class="tr-row d-flex align-items-center gap-2 fw-bold"><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2 5.5A1.5 1.5 0 0 1 3.5 4h4l2 2h7A1.5 1.5 0 0 1 18 7.5v7a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 2 14.5z"/></svg><span class="flex-grow-1" data-dd-name>Guides</span><span class="tr-n fw-bold text-body-secondary"><span data-dd-count="tr-guides">3</span> pages</span></div>
      <ul class="tr-pages d-grid gap-1 list-unstyled" id="tr-guides" data-dd-list="pages" aria-label="Guides">
        <li class="tr-page" data-dd-item><div class="tr-row d-flex align-items-center gap-2 fw-medium"><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="flex-grow-1" data-dd-name>Theming</span></div></li>
        <li class="tr-page" data-dd-item><div class="tr-row d-flex align-items-center gap-2 fw-medium"><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="flex-grow-1" data-dd-name>Forms and validation</span></div></li>
        <li class="tr-page" data-dd-item><div class="tr-row d-flex align-items-center gap-2 fw-medium"><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="flex-grow-1" data-dd-name>Deploying</span></div></li>
      </ul></li>
    <li class="tr-sec" data-dd-item><div class="tr-row d-flex align-items-center gap-2 fw-bold"><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2 5.5A1.5 1.5 0 0 1 3.5 4h4l2 2h7A1.5 1.5 0 0 1 18 7.5v7a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 2 14.5z"/></svg><span class="flex-grow-1" data-dd-name>Reference</span><span class="tr-n fw-bold text-body-secondary"><span data-dd-count="tr-ref">2</span> pages</span></div>
      <ul class="tr-pages d-grid gap-1 list-unstyled" id="tr-ref" data-dd-list="pages" aria-label="Reference">
        <li class="tr-page" data-dd-item><div class="tr-row d-flex align-items-center gap-2 fw-medium"><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="flex-grow-1" data-dd-name>CLI commands</span></div></li>
        <li class="tr-page" data-dd-item><div class="tr-row d-flex align-items-center gap-2 fw-medium"><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="tr-ico" width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="flex-grow-1" data-dd-name>Configuration file</span></div></li>
      </ul></li>
  </ul>
</section></div>

<div><span class="lbl">Priority ranking with numbers</span>
<section class="tr card" data-dd data-ranking aria-labelledby="rk-title">
  <p class="visually-hidden" aria-live="assertive" aria-atomic="true" data-dd-live></p>
  <div class="tr-head d-grid gap-1 mx-1 mb-3"><h2 class="fw-bold mb-0" id="rk-title">What matters most in your next laptop?</h2><p class="text-body-secondary mb-0" id="rk-hint">Drag a row, use the arrow buttons, or focus a handle, press Space and use Up and Down.</p></div>
  <ol class="d-grid gap-2 list-unstyled mb-0" data-dd-list="ranks">
    <li class="rk d-flex align-items-center gap-3" data-dd-item><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="rk-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="rk-no rounded-3 d-grid align-content-center text-center fw-bolder flex-shrink-0" data-rank aria-hidden="true">1</span><span class="rk-text d-grid flex-grow-1"><b data-dd-name>Battery life</b><small class="text-body-secondary">All day away from a charger</small></span><span class="btn-group"><button class="btn btn-outline-secondary d-grid" type="button" data-step="-1" aria-label="Move Battery life up"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="btn btn-outline-secondary d-grid" type="button" data-step="1" aria-label="Move Battery life down"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="rk d-flex align-items-center gap-3" data-dd-item><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="rk-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="rk-no rounded-3 d-grid align-content-center text-center fw-bolder flex-shrink-0" data-rank aria-hidden="true">2</span><span class="rk-text d-grid flex-grow-1"><b data-dd-name>Weight</b><small class="text-body-secondary">Under 1.3 kg to carry every day</small></span><span class="btn-group"><button class="btn btn-outline-secondary d-grid" type="button" data-step="-1" aria-label="Move Weight up"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="btn btn-outline-secondary d-grid" type="button" data-step="1" aria-label="Move Weight down"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="rk d-flex align-items-center gap-3" data-dd-item><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="rk-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="rk-no rounded-3 d-grid align-content-center text-center fw-bolder flex-shrink-0" data-rank aria-hidden="true">3</span><span class="rk-text d-grid flex-grow-1"><b data-dd-name>Screen</b><small class="text-body-secondary">Bright and colour-accurate</small></span><span class="btn-group"><button class="btn btn-outline-secondary d-grid" type="button" data-step="-1" aria-label="Move Screen up"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="btn btn-outline-secondary d-grid" type="button" data-step="1" aria-label="Move Screen down"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="rk d-flex align-items-center gap-3" data-dd-item><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="rk-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="rk-no rounded-3 d-grid align-content-center text-center fw-bolder flex-shrink-0" data-rank aria-hidden="true">4</span><span class="rk-text d-grid flex-grow-1"><b data-dd-name>Keyboard</b><small class="text-body-secondary">Comfortable for long writing sessions</small></span><span class="btn-group"><button class="btn btn-outline-secondary d-grid" type="button" data-step="-1" aria-label="Move Keyboard up"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="btn btn-outline-secondary d-grid" type="button" data-step="1" aria-label="Move Keyboard down"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="rk d-flex align-items-center gap-3" data-dd-item><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="rk-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="rk-no rounded-3 d-grid align-content-center text-center fw-bolder flex-shrink-0" data-rank aria-hidden="true">5</span><span class="rk-text d-grid flex-grow-1"><b data-dd-name>Price</b><small class="text-body-secondary">Stays under the budget</small></span><span class="btn-group"><button class="btn btn-outline-secondary d-grid" type="button" data-step="-1" aria-label="Move Price up"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="btn btn-outline-secondary d-grid" type="button" data-step="1" aria-label="Move Price down"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="rk d-flex align-items-center gap-3" data-dd-item><button class="tr-handle btn d-grid flex-shrink-0" type="button" data-dd-handle aria-describedby="rk-hint"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="rk-no rounded-3 d-grid align-content-center text-center fw-bolder flex-shrink-0" data-rank aria-hidden="true">6</span><span class="rk-text d-grid flex-grow-1"><b data-dd-name>Ports</b><small class="text-body-secondary">USB-C, HDMI and a card reader</small></span><span class="btn-group"><button class="btn btn-outline-secondary d-grid" type="button" data-step="-1" aria-label="Move Ports up"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="btn btn-outline-secondary d-grid" type="button" data-step="1" aria-label="Move Ports down"><svg class="m-auto" width="16" height="16" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
  </ol>
</section></div>
CSS
/* Nested reorder and ranking for Bootstrap 5: .card panels, .list-unstyled lists, .btn handles and a .btn-group of
   outline arrow buttons; these rules draw the tree lines, the rank badges and the states the script sets. */
.tr{--bs-card-border-color:#e4e7ec;--bs-card-border-radius:16px;--bs-secondary-color:#5d6679;--tr-accent:#2563eb;--tr-tint:#eaf1ff;color:#101828;
  padding:16px 16px 18px;box-shadow:0 1px 2px rgba(16,24,40,.05),0 10px 28px -16px rgba(16,24,40,.18)}
.tr-head h2{font-size:1rem;letter-spacing:-.01em}
.tr-head p{font-size:.82rem}
.tr-sec{border-radius:12px;padding:2px 0;cursor:grab;user-select:none}
.tr-row{padding:6px 8px 6px 4px;border-radius:10px;background:#fff}
.tr-ico{flex:none}
.tr-sec>.tr-row{font-size:.9rem}
.tr-sec>.tr-row .tr-ico{color:var(--tr-accent)}
.tr-n{font-size:.72rem}
.tr-pages{margin:2px 0 4px 21px;padding:2px 0 2px 14px;border-left:2px solid #e4e7ec;min-height:38px;border-radius:0 10px 10px 0;transition:background-color .15s}
.tr-pages[data-empty]::after{content:"Drop pages here";display:grid;place-items:center;height:34px;border:2px dashed #b3bbc8;border-radius:8px;font-size:.78rem;color:#5d6679}
.tr-pages[data-over]{background:var(--tr-tint);border-left-color:var(--tr-accent)}
.tr-page{border-radius:10px;cursor:grab}
.tr-page .tr-row{font-size:.86rem}
.tr-page .tr-ico{color:#667085}
.tr-handle{--bs-btn-padding-x:0;--bs-btn-padding-y:0;--bs-btn-color:#667085;--bs-btn-hover-bg:#f2f4f7;--bs-btn-hover-color:#101828;--bs-btn-active-bg:#f2f4f7;
  --bs-btn-border-width:0;--bs-btn-border-radius:8px;width:28px;height:30px;cursor:grab;touch-action:none}
.tr-handle:focus-visible,.rk .btn-group .btn:focus-visible{outline:2px solid var(--tr-accent);outline-offset:1px;box-shadow:none;z-index:3}
.rk{padding:10px 10px 10px 8px;border:1px solid #e4e7ec;border-radius:12px;background:#fff;cursor:grab;user-select:none}
.rk-no{width:32px;height:32px;font-size:.9rem;background:#f2f4f7;color:#344054;font-variant-numeric:tabular-nums}
.rk:nth-child(-n+3) .rk-no{background:var(--tr-accent);color:#fff}
.rk-text b{font-size:.92rem;font-weight:600}
.rk-text small{font-size:.78rem}
.rk .btn-group{gap:4px}
.rk .btn-group .btn{--bs-btn-padding-x:0;--bs-btn-padding-y:0;--bs-btn-color:#344054;--bs-btn-border-color:#d0d5dd;--bs-btn-hover-bg:#f2f4f7;--bs-btn-hover-color:#101828;--bs-btn-hover-border-color:#d0d5dd;
  --bs-btn-active-bg:#f2f4f7;--bs-btn-active-color:#101828;--bs-btn-active-border-color:#d0d5dd;width:30px;height:30px;border-radius:8px!important;margin:0!important}
.rk .btn[aria-disabled="true"]{color:#98a2b3;background:#f9fafb;cursor:default}
.tr-sec[data-dragging],.tr-page[data-dragging],.rk[data-dragging]{background:var(--tr-tint);outline:2px dashed #93b4f8;outline-offset:-2px}
.tr-sec[data-dragging]>*,.tr-page[data-dragging]>*,.rk[data-dragging]>*{visibility:hidden}
.tr-sec[data-dd-ghost],.tr-page[data-dd-ghost]{background:#fff;box-shadow:0 16px 36px -12px rgba(16,24,40,.4);rotate:1deg}
.rk[data-dd-ghost]{box-shadow:0 16px 36px -12px rgba(16,24,40,.4);rotate:1deg}
.tr-sec[data-grabbed]>.tr-row,.tr-page[data-grabbed]>.tr-row,.rk[data-grabbed]{background:var(--tr-tint);box-shadow:0 0 0 2px var(--tr-accent)}
@media (max-width:480px){.rk-text small{display:none!important}}
@media (prefers-reduced-motion:reduce){.tr-pages{transition:none}.tr-sec[data-dd-ghost],.tr-page[data-dd-ghost],.rk[data-dd-ghost]{rotate:none}}
JavaScript
// Ranking: renumber the ranks after every move, disable the arrow at either end, and let the
// Move up / Move down buttons reorder a row in one click (announced through the same live region).
document.addEventListener('dd:change', e => {
  const root = e.target; if (!root.matches('[data-ranking]')) return;
  const rows = [...root.querySelectorAll('[data-dd-item]')];
  rows.forEach((li, i) => {
    li.querySelector('[data-rank]').textContent = i + 1;
    li.querySelector('[data-step="-1"]').setAttribute('aria-disabled', i === 0);
    li.querySelector('[data-step="1"]').setAttribute('aria-disabled', i === rows.length - 1);
  });
});
document.addEventListener('click', e => {
  const b = e.target.closest('[data-step]'); if (!b || b.getAttribute('aria-disabled') === 'true') return;
  const li = b.closest('[data-dd-item]'), root = li.closest('[data-dd]'), up = b.dataset.step === '-1';
  const sib = up ? li.previousElementSibling : li.nextElementSibling; if (!sib) return;
  up ? sib.before(li) : sib.after(li); b.focus();
  root.dispatchEvent(new Event('dd:change', { bubbles: true }));
  const live = root.querySelector('[data-dd-live]'), n = [...li.parentElement.children].indexOf(li) + 1;
  live.textContent = li.querySelector('[data-dd-name]').textContent + ' moved to rank ' + n + ' of ' + li.parentElement.children.length + '.';
});
// Drag and drop with pointer events (mouse, touch and pen) plus a keyboard mode on every handle:
// Space or Enter picks an item up, the arrow keys move it, Space or Enter drops it, Escape puts it back.
// Every move is announced in the component's aria-live region. Markup contract: [data-dd] root,
// [data-dd-list="group"] lists, [data-dd-item] items with a [data-dd-name] and a [data-dd-handle] button.
// data-dd="grid" or "wrap" compares the pointer horizontally; in a grid, Up and Down jump a whole row.
document.querySelectorAll('[data-dd]').forEach(root => {
  const grid = root.dataset.dd === 'grid', horiz = grid || root.dataset.dd === 'wrap', live = root.querySelector('[data-dd-live]');
  const items = l => [...l.children].filter(c => c.hasAttribute('data-dd-item'));
  const lists = l => [...root.querySelectorAll(`[data-dd-list="${l.dataset.ddList}"]`)];
  const name = el => el.querySelector('[data-dd-name]').textContent.trim();
  const full = l => !!l.dataset.ddMax && items(l).length >= +l.dataset.ddMax;
  const pos = el => { const l = el.parentElement, all = items(l);
    return (lists(l).length > 1 ? l.getAttribute('aria-label') + ', ' : '') + 'position ' + (all.indexOf(el) + 1) + ' of ' + all.length; };
  const say = msg => { live.textContent = ''; setTimeout(() => { live.textContent = msg; }, 60); };
  const clearOver = keep => root.querySelectorAll('[data-over]').forEach(l => l !== keep && l.removeAttribute('data-over'));
  const marks = () => root.querySelectorAll('[data-dd-list]').forEach(l => { const n = items(l).length, c = root.querySelector(`[data-dd-count="${l.id}"]`);
    l.toggleAttribute('data-empty', !n); l.toggleAttribute('data-full', full(l)); if (c) c.textContent = n; });
  const changed = () => {
    marks();
    root.querySelectorAll('[data-dd-item]').forEach(it => it.querySelector('[data-dd-handle]').setAttribute('aria-label', 'Move ' + name(it)));
    root.dispatchEvent(new Event('dd:change', { bubbles: true }));
  };
  let drag, ghost, home, kb, kbh, sx, sy, ox, oy, pid, dragged, moving;
  const putBack = el => home[0].insertBefore(el, home[1] && home[1].parentNode === home[0] ? home[1] : null);

  root.addEventListener('pointerdown', e => {
    const item = e.target.closest('[data-dd-item]'), handle = e.target.closest('[data-dd-handle]');
    if (!item || e.button > 0 || kb) return;
    if (!handle && (e.pointerType !== 'mouse' || 'ddHandleOnly' in root.dataset || e.target.closest('a,button,input,select,textarea,label'))) return;
    e.preventDefault(); drag = item; pid = e.pointerId; sx = e.clientX; sy = e.clientY; ghost = null;
  });
  root.addEventListener('pointermove', e => {
    if (!drag || e.pointerId !== pid) return;
    if (!ghost) { if (Math.hypot(e.clientX - sx, e.clientY - sy) < 6) return; start(); root.setPointerCapture(pid); }
    ghost.style.translate = `${e.clientX - ox}px ${e.clientY - oy}px`;
    if (e.clientY < 40) scrollBy(0, -12); else if (e.clientY > innerHeight - 40) scrollBy(0, 12);
    const hit = document.elementFromPoint(e.clientX, e.clientY), sel = `[data-dd-list="${home[0].dataset.ddList}"]`;
    const list = hit && (hit.closest(sel) || hit.closest('[data-dd-zone]')?.querySelector(sel)); // a [data-dd-zone] wrapper widens the target
    clearOver(list);
    if (!list || !root.contains(list) || drag.contains(list)) return;
    list.setAttribute('data-over', '');
    if (list !== drag.parentElement && full(list)) return;
    const over = items(list).find(c => c !== drag && c.contains(hit));
    if (over) { const r = over.getBoundingClientRect();
      const ref = (horiz ? e.clientX > r.left + r.width / 2 : e.clientY > r.top + r.height / 2) ? over.nextSibling : over;
      if (ref !== drag && ref !== drag.nextSibling) { list.insertBefore(drag, ref); marks(); }
    } else if (list !== drag.parentElement && !drag.contains(hit)) { list.append(drag); marks(); }
  });
  const end = e => {
    if (!drag || e.pointerId !== pid) return;
    if (ghost) {
      ghost.remove(); drag.removeAttribute('data-dragging'); root.removeAttribute('data-dd-active'); document.body.style.cursor = ''; clearOver();
      if (e.type === 'pointercancel') putBack(drag); else say(name(drag) + ' moved to ' + pos(drag) + '.');
      changed(); dragged = true; setTimeout(() => { dragged = false; });
    }
    drag = null;
  };
  root.addEventListener('pointerup', end); root.addEventListener('pointercancel', end);
  function start() {
    const r = drag.getBoundingClientRect(); ox = sx - r.left; oy = sy - r.top; home = [drag.parentElement, drag.nextSibling];
    ghost = drag.cloneNode(true); ghost.removeAttribute('data-dd-item'); ghost.setAttribute('data-dd-ghost', ''); ghost.setAttribute('aria-hidden', 'true'); ghost.inert = true;
    ghost.querySelectorAll('[id]').forEach(n => n.removeAttribute('id'));
    Object.assign(ghost.style, { position: 'fixed', left: 0, top: 0, width: r.width + 'px', height: r.height + 'px', margin: 0, zIndex: 999, pointerEvents: 'none' });
    drag.after(ghost); drag.setAttribute('data-dragging', ''); root.setAttribute('data-dd-active', ''); document.body.style.cursor = 'grabbing';
  }

  // Keyboard and screen reader mode: the handle is a toggle button (aria-pressed) that picks the item up.
  const finish = msg => { kb.removeAttribute('data-grabbed'); kbh.setAttribute('aria-pressed', 'false'); root.removeAttribute('data-dd-active'); clearOver(); kb = null; changed(); say(msg); };
  const move = fn => { moving = true; fn(); kbh.focus(); moving = false; kbh.scrollIntoView({ block: 'nearest' }); clearOver(kb.parentElement); kb.parentElement.setAttribute('data-over', ''); changed(); say(name(kb) + ' moved to ' + pos(kb) + '.'); };
  root.addEventListener('click', e => {
    const h = e.target.closest('[data-dd-handle]'); if (!h || dragged) return;
    const item = h.closest('[data-dd-item]');
    if (kb === item) return finish(name(item) + ' dropped, ' + pos(item) + '.');
    if (kb) finish(name(kb) + ' dropped, ' + pos(kb) + '.');
    kb = item; kbh = h; home = [item.parentElement, item.nextSibling];
    item.setAttribute('data-grabbed', ''); h.setAttribute('aria-pressed', 'true'); root.setAttribute('data-dd-active', ''); item.parentElement.setAttribute('data-over', ''); h.focus();
    say(name(item) + ' picked up, ' + pos(item) + '. Use the arrow keys to move it, Space to drop it, Escape to cancel.');
  });
  root.addEventListener('keydown', e => {
    if (!kb || e.target !== kbh) return;
    if (e.key === 'Escape') { e.preventDefault(); moving = true; putBack(kb); kbh.focus(); moving = false; return finish('Move cancelled. ' + name(kb) + ' returned to ' + pos(kb) + '.'); }
    const d = { ArrowUp: -1, ArrowLeft: -1, ArrowDown: 1, ArrowRight: 1 }[e.key]; if (!d) return;
    e.preventDefault();
    const l = kb.parentElement, all = items(l), i = all.indexOf(kb), ls = lists(l), side = e.key === 'ArrowLeft' || e.key === 'ArrowRight';
    if (side && ls.length > 1) {
      const t = ls[ls.indexOf(l) + d];
      if (!t) return say(name(kb) + ' is already in ' + l.getAttribute('aria-label') + ', the ' + (d < 0 ? 'first' : 'last') + ' list.');
      if (full(t)) return say(t.getAttribute('aria-label') + ' is full.');
      return move(() => t.insertBefore(kb, items(t)[i] || null));
    }
    const step = grid && !side ? d * all.filter(x => x.offsetTop === all[0].offsetTop).length : d, j = Math.max(0, Math.min(all.length - 1, i + step));
    if (j === i) return say(name(kb) + ' is already ' + (d < 0 ? 'first' : 'last') + '.');
    move(() => l.insertBefore(kb, j > i ? all[j].nextSibling : all[j]));
  });
  root.addEventListener('focusout', e => { if (kb && !moving && e.target === kbh) finish(name(kb) + ' dropped, ' + pos(kb) + '.'); });
  changed();
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-tr-muted uppercase">Nested outline: sections and pages</span>
<section class="rounded-2xl border border-tr-line bg-white px-4 pt-4 pb-[18px] text-tr-ink shadow-[0_1px_2px_rgba(16,24,40,.05),0_10px_28px_-16px_rgba(16,24,40,.18)]" data-dd aria-labelledby="tr-title">
  <div class="mx-1 mt-0.5 mb-3 grid gap-0.5"><h2 class="font-bold tracking-[-.01em]" id="tr-title">Docs navigation</h2><p class="text-[.82rem] text-tr-muted" id="tr-hint">Drag sections or pages. With the keyboard: focus a handle, press Space, use Up and Down; Left and Right move a page to the previous or next section.</p></div>
  <ul class="grid gap-1.5 *:data-dragging:bg-tr-tint *:data-dragging:outline-2 *:data-dragging:-outline-offset-2 *:data-dragging:outline-dashed *:data-dragging:outline-[#93b4f8] [&>[data-dragging]>*]:invisible *:data-dd-ghost:bg-white *:data-dd-ghost:rotate-1 *:data-dd-ghost:shadow-[0_16px_36px_-12px_rgba(16,24,40,.4)] motion-reduce:*:data-dd-ghost:rotate-0 [&>[data-grabbed]>div]:bg-tr-tint [&>[data-grabbed]>div]:shadow-[0_0_0_2px_#2563eb]" data-dd-list="sections" aria-label="Sections">
    <li class="cursor-grab rounded-xl py-0.5 select-none" data-dd-item><div class="flex items-center gap-2 rounded-[10px] bg-white py-1.5 pr-2 pl-1 text-[.9rem] font-bold"><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-tr-accent" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2 5.5A1.5 1.5 0 0 1 3.5 4h4l2 2h7A1.5 1.5 0 0 1 18 7.5v7a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 2 14.5z"/></svg><span class="min-w-0 flex-1" data-dd-name>Getting started</span><span class="text-[.72rem] font-bold text-tr-muted"><span data-dd-count="tr-start">3</span> pages</span></div>
      <ul class="ml-[21px] mt-0.5 mb-1 grid min-h-[38px] gap-0.5 rounded-r-[10px] border-l-2 border-tr-line py-0.5 pl-3.5 transition-colors motion-reduce:transition-none data-over:border-tr-accent data-over:bg-tr-tint data-empty:after:grid data-empty:after:h-[34px] data-empty:after:place-items-center data-empty:after:rounded-lg data-empty:after:border-2 data-empty:after:border-dashed data-empty:after:border-[#b3bbc8] data-empty:after:text-[.78rem] data-empty:after:text-tr-muted data-empty:after:content-['Drop_pages_here'] *:data-dragging:bg-tr-tint *:data-dragging:outline-2 *:data-dragging:-outline-offset-2 *:data-dragging:outline-dashed *:data-dragging:outline-[#93b4f8] [&>[data-dragging]>*]:invisible *:data-dd-ghost:bg-white *:data-dd-ghost:rotate-1 *:data-dd-ghost:shadow-[0_16px_36px_-12px_rgba(16,24,40,.4)] motion-reduce:*:data-dd-ghost:rotate-0 [&>[data-grabbed]>div]:bg-tr-tint [&>[data-grabbed]>div]:shadow-[0_0_0_2px_#2563eb]" id="tr-start" data-dd-list="pages" aria-label="Getting started">
        <li class="cursor-grab rounded-[10px]" data-dd-item><div class="flex items-center gap-2 rounded-[10px] bg-white py-1.5 pr-2 pl-1 text-[.86rem] font-medium"><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-[#667085]" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="min-w-0 flex-1" data-dd-name>Installation</span></div></li>
        <li class="cursor-grab rounded-[10px]" data-dd-item><div class="flex items-center gap-2 rounded-[10px] bg-white py-1.5 pr-2 pl-1 text-[.86rem] font-medium"><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-[#667085]" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="min-w-0 flex-1" data-dd-name>Quick start</span></div></li>
        <li class="cursor-grab rounded-[10px]" data-dd-item><div class="flex items-center gap-2 rounded-[10px] bg-white py-1.5 pr-2 pl-1 text-[.86rem] font-medium"><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-[#667085]" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="min-w-0 flex-1" data-dd-name>Project structure</span></div></li>
      </ul></li>
    <li class="cursor-grab rounded-xl py-0.5 select-none" data-dd-item><div class="flex items-center gap-2 rounded-[10px] bg-white py-1.5 pr-2 pl-1 text-[.9rem] font-bold"><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-tr-accent" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2 5.5A1.5 1.5 0 0 1 3.5 4h4l2 2h7A1.5 1.5 0 0 1 18 7.5v7a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 2 14.5z"/></svg><span class="min-w-0 flex-1" data-dd-name>Guides</span><span class="text-[.72rem] font-bold text-tr-muted"><span data-dd-count="tr-guides">3</span> pages</span></div>
      <ul class="ml-[21px] mt-0.5 mb-1 grid min-h-[38px] gap-0.5 rounded-r-[10px] border-l-2 border-tr-line py-0.5 pl-3.5 transition-colors motion-reduce:transition-none data-over:border-tr-accent data-over:bg-tr-tint data-empty:after:grid data-empty:after:h-[34px] data-empty:after:place-items-center data-empty:after:rounded-lg data-empty:after:border-2 data-empty:after:border-dashed data-empty:after:border-[#b3bbc8] data-empty:after:text-[.78rem] data-empty:after:text-tr-muted data-empty:after:content-['Drop_pages_here'] *:data-dragging:bg-tr-tint *:data-dragging:outline-2 *:data-dragging:-outline-offset-2 *:data-dragging:outline-dashed *:data-dragging:outline-[#93b4f8] [&>[data-dragging]>*]:invisible *:data-dd-ghost:bg-white *:data-dd-ghost:rotate-1 *:data-dd-ghost:shadow-[0_16px_36px_-12px_rgba(16,24,40,.4)] motion-reduce:*:data-dd-ghost:rotate-0 [&>[data-grabbed]>div]:bg-tr-tint [&>[data-grabbed]>div]:shadow-[0_0_0_2px_#2563eb]" id="tr-guides" data-dd-list="pages" aria-label="Guides">
        <li class="cursor-grab rounded-[10px]" data-dd-item><div class="flex items-center gap-2 rounded-[10px] bg-white py-1.5 pr-2 pl-1 text-[.86rem] font-medium"><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-[#667085]" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="min-w-0 flex-1" data-dd-name>Theming</span></div></li>
        <li class="cursor-grab rounded-[10px]" data-dd-item><div class="flex items-center gap-2 rounded-[10px] bg-white py-1.5 pr-2 pl-1 text-[.86rem] font-medium"><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-[#667085]" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="min-w-0 flex-1" data-dd-name>Forms and validation</span></div></li>
        <li class="cursor-grab rounded-[10px]" data-dd-item><div class="flex items-center gap-2 rounded-[10px] bg-white py-1.5 pr-2 pl-1 text-[.86rem] font-medium"><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-[#667085]" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="min-w-0 flex-1" data-dd-name>Deploying</span></div></li>
      </ul></li>
    <li class="cursor-grab rounded-xl py-0.5 select-none" data-dd-item><div class="flex items-center gap-2 rounded-[10px] bg-white py-1.5 pr-2 pl-1 text-[.9rem] font-bold"><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-tr-accent" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2 5.5A1.5 1.5 0 0 1 3.5 4h4l2 2h7A1.5 1.5 0 0 1 18 7.5v7a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 2 14.5z"/></svg><span class="min-w-0 flex-1" data-dd-name>Reference</span><span class="text-[.72rem] font-bold text-tr-muted"><span data-dd-count="tr-ref">2</span> pages</span></div>
      <ul class="ml-[21px] mt-0.5 mb-1 grid min-h-[38px] gap-0.5 rounded-r-[10px] border-l-2 border-tr-line py-0.5 pl-3.5 transition-colors motion-reduce:transition-none data-over:border-tr-accent data-over:bg-tr-tint data-empty:after:grid data-empty:after:h-[34px] data-empty:after:place-items-center data-empty:after:rounded-lg data-empty:after:border-2 data-empty:after:border-dashed data-empty:after:border-[#b3bbc8] data-empty:after:text-[.78rem] data-empty:after:text-tr-muted data-empty:after:content-['Drop_pages_here'] *:data-dragging:bg-tr-tint *:data-dragging:outline-2 *:data-dragging:-outline-offset-2 *:data-dragging:outline-dashed *:data-dragging:outline-[#93b4f8] [&>[data-dragging]>*]:invisible *:data-dd-ghost:bg-white *:data-dd-ghost:rotate-1 *:data-dd-ghost:shadow-[0_16px_36px_-12px_rgba(16,24,40,.4)] motion-reduce:*:data-dd-ghost:rotate-0 [&>[data-grabbed]>div]:bg-tr-tint [&>[data-grabbed]>div]:shadow-[0_0_0_2px_#2563eb]" id="tr-ref" data-dd-list="pages" aria-label="Reference">
        <li class="cursor-grab rounded-[10px]" data-dd-item><div class="flex items-center gap-2 rounded-[10px] bg-white py-1.5 pr-2 pl-1 text-[.86rem] font-medium"><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-[#667085]" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="min-w-0 flex-1" data-dd-name>CLI commands</span></div></li>
        <li class="cursor-grab rounded-[10px]" data-dd-item><div class="flex items-center gap-2 rounded-[10px] bg-white py-1.5 pr-2 pl-1 text-[.86rem] font-medium"><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="tr-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><svg class="size-[18px] flex-none text-[#667085]" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="M5 2.5h6.5L15 6v11.5H5zM11 2.5V6.5h4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg><span class="min-w-0 flex-1" data-dd-name>Configuration file</span></div></li>
      </ul></li>
  </ul>
  <p class="sr-only" aria-live="assertive" aria-atomic="true" data-dd-live></p>
</section></div>

<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-tr-muted uppercase">Priority ranking with numbers</span>
<section class="rounded-2xl border border-tr-line bg-white px-4 pt-4 pb-[18px] text-tr-ink shadow-[0_1px_2px_rgba(16,24,40,.05),0_10px_28px_-16px_rgba(16,24,40,.18)]" data-dd data-ranking aria-labelledby="rk-title">
  <div class="mx-1 mt-0.5 mb-3 grid gap-0.5"><h2 class="font-bold tracking-[-.01em]" id="rk-title">What matters most in your next laptop?</h2><p class="text-[.82rem] text-tr-muted" id="rk-hint">Drag a row, use the arrow buttons, or focus a handle, press Space and use Up and Down.</p></div>
  <ol class="grid gap-2 *:data-dragging:bg-tr-tint *:data-dragging:outline-2 *:data-dragging:-outline-offset-2 *:data-dragging:outline-dashed *:data-dragging:outline-[#93b4f8] [&>[data-dragging]>*]:invisible *:data-dd-ghost:bg-white *:data-dd-ghost:rotate-1 *:data-dd-ghost:shadow-[0_16px_36px_-12px_rgba(16,24,40,.4)] motion-reduce:*:data-dd-ghost:rotate-0 *:data-grabbed:bg-tr-tint *:data-grabbed:shadow-[0_0_0_2px_#2563eb]" data-dd-list="ranks">
    <li class="group flex cursor-grab items-center gap-3 rounded-xl [&:nth-child(-n+3)_[data-rank]]:bg-tr-accent [&:nth-child(-n+3)_[data-rank]]:text-white border border-tr-line bg-white py-2.5 pr-2.5 pl-2 select-none" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="rk-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="grid size-8 flex-none place-items-center rounded-[10px] bg-[#f2f4f7] text-[.9rem] font-extrabold text-[#344054] tabular-nums " data-rank aria-hidden="true">1</span><span class="grid min-w-0 flex-1"><b class="text-[.92rem] font-semibold" data-dd-name>Battery life</b><small class="text-[.78rem] text-tr-muted max-[480px]:hidden">All day away from a charger</small></span><span class="flex gap-1"><button class="grid size-[30px] cursor-pointer place-items-center rounded-lg border border-[#d0d5dd] bg-white text-[#344054] hover:bg-[#f2f4f7] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent aria-disabled:cursor-default aria-disabled:bg-[#f9fafb] aria-disabled:text-[#98a2b3]" type="button" data-step="-1" aria-label="Move Battery life up"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="grid size-[30px] cursor-pointer place-items-center rounded-lg border border-[#d0d5dd] bg-white text-[#344054] hover:bg-[#f2f4f7] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent aria-disabled:cursor-default aria-disabled:bg-[#f9fafb] aria-disabled:text-[#98a2b3]" type="button" data-step="1" aria-label="Move Battery life down"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="group flex cursor-grab items-center gap-3 rounded-xl [&:nth-child(-n+3)_[data-rank]]:bg-tr-accent [&:nth-child(-n+3)_[data-rank]]:text-white border border-tr-line bg-white py-2.5 pr-2.5 pl-2 select-none" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="rk-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="grid size-8 flex-none place-items-center rounded-[10px] bg-[#f2f4f7] text-[.9rem] font-extrabold text-[#344054] tabular-nums " data-rank aria-hidden="true">2</span><span class="grid min-w-0 flex-1"><b class="text-[.92rem] font-semibold" data-dd-name>Weight</b><small class="text-[.78rem] text-tr-muted max-[480px]:hidden">Under 1.3 kg to carry every day</small></span><span class="flex gap-1"><button class="grid size-[30px] cursor-pointer place-items-center rounded-lg border border-[#d0d5dd] bg-white text-[#344054] hover:bg-[#f2f4f7] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent aria-disabled:cursor-default aria-disabled:bg-[#f9fafb] aria-disabled:text-[#98a2b3]" type="button" data-step="-1" aria-label="Move Weight up"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="grid size-[30px] cursor-pointer place-items-center rounded-lg border border-[#d0d5dd] bg-white text-[#344054] hover:bg-[#f2f4f7] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent aria-disabled:cursor-default aria-disabled:bg-[#f9fafb] aria-disabled:text-[#98a2b3]" type="button" data-step="1" aria-label="Move Weight down"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="group flex cursor-grab items-center gap-3 rounded-xl [&:nth-child(-n+3)_[data-rank]]:bg-tr-accent [&:nth-child(-n+3)_[data-rank]]:text-white border border-tr-line bg-white py-2.5 pr-2.5 pl-2 select-none" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="rk-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="grid size-8 flex-none place-items-center rounded-[10px] bg-[#f2f4f7] text-[.9rem] font-extrabold text-[#344054] tabular-nums " data-rank aria-hidden="true">3</span><span class="grid min-w-0 flex-1"><b class="text-[.92rem] font-semibold" data-dd-name>Screen</b><small class="text-[.78rem] text-tr-muted max-[480px]:hidden">Bright and colour-accurate</small></span><span class="flex gap-1"><button class="grid size-[30px] cursor-pointer place-items-center rounded-lg border border-[#d0d5dd] bg-white text-[#344054] hover:bg-[#f2f4f7] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent aria-disabled:cursor-default aria-disabled:bg-[#f9fafb] aria-disabled:text-[#98a2b3]" type="button" data-step="-1" aria-label="Move Screen up"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="grid size-[30px] cursor-pointer place-items-center rounded-lg border border-[#d0d5dd] bg-white text-[#344054] hover:bg-[#f2f4f7] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent aria-disabled:cursor-default aria-disabled:bg-[#f9fafb] aria-disabled:text-[#98a2b3]" type="button" data-step="1" aria-label="Move Screen down"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="group flex cursor-grab items-center gap-3 rounded-xl [&:nth-child(-n+3)_[data-rank]]:bg-tr-accent [&:nth-child(-n+3)_[data-rank]]:text-white border border-tr-line bg-white py-2.5 pr-2.5 pl-2 select-none" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="rk-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="grid size-8 flex-none place-items-center rounded-[10px] bg-[#f2f4f7] text-[.9rem] font-extrabold text-[#344054] tabular-nums " data-rank aria-hidden="true">4</span><span class="grid min-w-0 flex-1"><b class="text-[.92rem] font-semibold" data-dd-name>Keyboard</b><small class="text-[.78rem] text-tr-muted max-[480px]:hidden">Comfortable for long writing sessions</small></span><span class="flex gap-1"><button class="grid size-[30px] cursor-pointer place-items-center rounded-lg border border-[#d0d5dd] bg-white text-[#344054] hover:bg-[#f2f4f7] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent aria-disabled:cursor-default aria-disabled:bg-[#f9fafb] aria-disabled:text-[#98a2b3]" type="button" data-step="-1" aria-label="Move Keyboard up"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="grid size-[30px] cursor-pointer place-items-center rounded-lg border border-[#d0d5dd] bg-white text-[#344054] hover:bg-[#f2f4f7] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent aria-disabled:cursor-default aria-disabled:bg-[#f9fafb] aria-disabled:text-[#98a2b3]" type="button" data-step="1" aria-label="Move Keyboard down"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="group flex cursor-grab items-center gap-3 rounded-xl [&:nth-child(-n+3)_[data-rank]]:bg-tr-accent [&:nth-child(-n+3)_[data-rank]]:text-white border border-tr-line bg-white py-2.5 pr-2.5 pl-2 select-none" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="rk-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="grid size-8 flex-none place-items-center rounded-[10px] bg-[#f2f4f7] text-[.9rem] font-extrabold text-[#344054] tabular-nums " data-rank aria-hidden="true">5</span><span class="grid min-w-0 flex-1"><b class="text-[.92rem] font-semibold" data-dd-name>Price</b><small class="text-[.78rem] text-tr-muted max-[480px]:hidden">Stays under the budget</small></span><span class="flex gap-1"><button class="grid size-[30px] cursor-pointer place-items-center rounded-lg border border-[#d0d5dd] bg-white text-[#344054] hover:bg-[#f2f4f7] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent aria-disabled:cursor-default aria-disabled:bg-[#f9fafb] aria-disabled:text-[#98a2b3]" type="button" data-step="-1" aria-label="Move Price up"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="grid size-[30px] cursor-pointer place-items-center rounded-lg border border-[#d0d5dd] bg-white text-[#344054] hover:bg-[#f2f4f7] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent aria-disabled:cursor-default aria-disabled:bg-[#f9fafb] aria-disabled:text-[#98a2b3]" type="button" data-step="1" aria-label="Move Price down"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
    <li class="group flex cursor-grab items-center gap-3 rounded-xl [&:nth-child(-n+3)_[data-rank]]:bg-tr-accent [&:nth-child(-n+3)_[data-rank]]:text-white border border-tr-line bg-white py-2.5 pr-2.5 pl-2 select-none" data-dd-item><button class="grid h-[30px] w-7 flex-none cursor-grab touch-none place-items-center rounded-lg text-[#667085] hover:bg-[#f2f4f7] hover:text-tr-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent" type="button" data-dd-handle aria-describedby="rk-hint"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><circle cx="7" cy="5" r="1.5"/><circle cx="13" cy="5" r="1.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="10" r="1.5"/><circle cx="7" cy="15" r="1.5"/><circle cx="13" cy="15" r="1.5"/></svg></button><span class="grid size-8 flex-none place-items-center rounded-[10px] bg-[#f2f4f7] text-[.9rem] font-extrabold text-[#344054] tabular-nums " data-rank aria-hidden="true">6</span><span class="grid min-w-0 flex-1"><b class="text-[.92rem] font-semibold" data-dd-name>Ports</b><small class="text-[.78rem] text-tr-muted max-[480px]:hidden">USB-C, HDMI and a card reader</small></span><span class="flex gap-1"><button class="grid size-[30px] cursor-pointer place-items-center rounded-lg border border-[#d0d5dd] bg-white text-[#344054] hover:bg-[#f2f4f7] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent aria-disabled:cursor-default aria-disabled:bg-[#f9fafb] aria-disabled:text-[#98a2b3]" type="button" data-step="-1" aria-label="Move Ports up"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 12 5-5 5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button><button class="grid size-[30px] cursor-pointer place-items-center rounded-lg border border-[#d0d5dd] bg-white text-[#344054] hover:bg-[#f2f4f7] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-tr-accent aria-disabled:cursor-default aria-disabled:bg-[#f9fafb] aria-disabled:text-[#98a2b3]" type="button" data-step="1" aria-label="Move Ports down"><svg class="size-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" focusable="false"><path d="m5 8 5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button></span></li>
  </ol>
  <p class="sr-only" aria-live="assertive" aria-atomic="true" data-dd-live></p>
</section></div>
CSS
@theme {
  --color-tr-accent: #2563eb;
  --color-tr-tint: #eaf1ff;
  --color-tr-line: #e4e7ec;
  --color-tr-muted: #5d6679;
  --color-tr-ink: #101828;
}
JavaScript
// Ranking: renumber the ranks after every move, disable the arrow at either end, and let the
// Move up / Move down buttons reorder a row in one click (announced through the same live region).
document.addEventListener('dd:change', e => {
  const root = e.target; if (!root.matches('[data-ranking]')) return;
  const rows = [...root.querySelectorAll('[data-dd-item]')];
  rows.forEach((li, i) => {
    li.querySelector('[data-rank]').textContent = i + 1;
    li.querySelector('[data-step="-1"]').setAttribute('aria-disabled', i === 0);
    li.querySelector('[data-step="1"]').setAttribute('aria-disabled', i === rows.length - 1);
  });
});
document.addEventListener('click', e => {
  const b = e.target.closest('[data-step]'); if (!b || b.getAttribute('aria-disabled') === 'true') return;
  const li = b.closest('[data-dd-item]'), root = li.closest('[data-dd]'), up = b.dataset.step === '-1';
  const sib = up ? li.previousElementSibling : li.nextElementSibling; if (!sib) return;
  up ? sib.before(li) : sib.after(li); b.focus();
  root.dispatchEvent(new Event('dd:change', { bubbles: true }));
  const live = root.querySelector('[data-dd-live]'), n = [...li.parentElement.children].indexOf(li) + 1;
  live.textContent = li.querySelector('[data-dd-name]').textContent + ' moved to rank ' + n + ' of ' + li.parentElement.children.length + '.';
});
// Drag and drop with pointer events (mouse, touch and pen) plus a keyboard mode on every handle:
// Space or Enter picks an item up, the arrow keys move it, Space or Enter drops it, Escape puts it back.
// Every move is announced in the component's aria-live region. Markup contract: [data-dd] root,
// [data-dd-list="group"] lists, [data-dd-item] items with a [data-dd-name] and a [data-dd-handle] button.
// data-dd="grid" or "wrap" compares the pointer horizontally; in a grid, Up and Down jump a whole row.
document.querySelectorAll('[data-dd]').forEach(root => {
  const grid = root.dataset.dd === 'grid', horiz = grid || root.dataset.dd === 'wrap', live = root.querySelector('[data-dd-live]');
  const items = l => [...l.children].filter(c => c.hasAttribute('data-dd-item'));
  const lists = l => [...root.querySelectorAll(`[data-dd-list="${l.dataset.ddList}"]`)];
  const name = el => el.querySelector('[data-dd-name]').textContent.trim();
  const full = l => !!l.dataset.ddMax && items(l).length >= +l.dataset.ddMax;
  const pos = el => { const l = el.parentElement, all = items(l);
    return (lists(l).length > 1 ? l.getAttribute('aria-label') + ', ' : '') + 'position ' + (all.indexOf(el) + 1) + ' of ' + all.length; };
  const say = msg => { live.textContent = ''; setTimeout(() => { live.textContent = msg; }, 60); };
  const clearOver = keep => root.querySelectorAll('[data-over]').forEach(l => l !== keep && l.removeAttribute('data-over'));
  const marks = () => root.querySelectorAll('[data-dd-list]').forEach(l => { const n = items(l).length, c = root.querySelector(`[data-dd-count="${l.id}"]`);
    l.toggleAttribute('data-empty', !n); l.toggleAttribute('data-full', full(l)); if (c) c.textContent = n; });
  const changed = () => {
    marks();
    root.querySelectorAll('[data-dd-item]').forEach(it => it.querySelector('[data-dd-handle]').setAttribute('aria-label', 'Move ' + name(it)));
    root.dispatchEvent(new Event('dd:change', { bubbles: true }));
  };
  let drag, ghost, home, kb, kbh, sx, sy, ox, oy, pid, dragged, moving;
  const putBack = el => home[0].insertBefore(el, home[1] && home[1].parentNode === home[0] ? home[1] : null);

  root.addEventListener('pointerdown', e => {
    const item = e.target.closest('[data-dd-item]'), handle = e.target.closest('[data-dd-handle]');
    if (!item || e.button > 0 || kb) return;
    if (!handle && (e.pointerType !== 'mouse' || 'ddHandleOnly' in root.dataset || e.target.closest('a,button,input,select,textarea,label'))) return;
    e.preventDefault(); drag = item; pid = e.pointerId; sx = e.clientX; sy = e.clientY; ghost = null;
  });
  root.addEventListener('pointermove', e => {
    if (!drag || e.pointerId !== pid) return;
    if (!ghost) { if (Math.hypot(e.clientX - sx, e.clientY - sy) < 6) return; start(); root.setPointerCapture(pid); }
    ghost.style.translate = `${e.clientX - ox}px ${e.clientY - oy}px`;
    if (e.clientY < 40) scrollBy(0, -12); else if (e.clientY > innerHeight - 40) scrollBy(0, 12);
    const hit = document.elementFromPoint(e.clientX, e.clientY), sel = `[data-dd-list="${home[0].dataset.ddList}"]`;
    const list = hit && (hit.closest(sel) || hit.closest('[data-dd-zone]')?.querySelector(sel)); // a [data-dd-zone] wrapper widens the target
    clearOver(list);
    if (!list || !root.contains(list) || drag.contains(list)) return;
    list.setAttribute('data-over', '');
    if (list !== drag.parentElement && full(list)) return;
    const over = items(list).find(c => c !== drag && c.contains(hit));
    if (over) { const r = over.getBoundingClientRect();
      const ref = (horiz ? e.clientX > r.left + r.width / 2 : e.clientY > r.top + r.height / 2) ? over.nextSibling : over;
      if (ref !== drag && ref !== drag.nextSibling) { list.insertBefore(drag, ref); marks(); }
    } else if (list !== drag.parentElement && !drag.contains(hit)) { list.append(drag); marks(); }
  });
  const end = e => {
    if (!drag || e.pointerId !== pid) return;
    if (ghost) {
      ghost.remove(); drag.removeAttribute('data-dragging'); root.removeAttribute('data-dd-active'); document.body.style.cursor = ''; clearOver();
      if (e.type === 'pointercancel') putBack(drag); else say(name(drag) + ' moved to ' + pos(drag) + '.');
      changed(); dragged = true; setTimeout(() => { dragged = false; });
    }
    drag = null;
  };
  root.addEventListener('pointerup', end); root.addEventListener('pointercancel', end);
  function start() {
    const r = drag.getBoundingClientRect(); ox = sx - r.left; oy = sy - r.top; home = [drag.parentElement, drag.nextSibling];
    ghost = drag.cloneNode(true); ghost.removeAttribute('data-dd-item'); ghost.setAttribute('data-dd-ghost', ''); ghost.setAttribute('aria-hidden', 'true'); ghost.inert = true;
    ghost.querySelectorAll('[id]').forEach(n => n.removeAttribute('id'));
    Object.assign(ghost.style, { position: 'fixed', left: 0, top: 0, width: r.width + 'px', height: r.height + 'px', margin: 0, zIndex: 999, pointerEvents: 'none' });
    drag.after(ghost); drag.setAttribute('data-dragging', ''); root.setAttribute('data-dd-active', ''); document.body.style.cursor = 'grabbing';
  }

  // Keyboard and screen reader mode: the handle is a toggle button (aria-pressed) that picks the item up.
  const finish = msg => { kb.removeAttribute('data-grabbed'); kbh.setAttribute('aria-pressed', 'false'); root.removeAttribute('data-dd-active'); clearOver(); kb = null; changed(); say(msg); };
  const move = fn => { moving = true; fn(); kbh.focus(); moving = false; kbh.scrollIntoView({ block: 'nearest' }); clearOver(kb.parentElement); kb.parentElement.setAttribute('data-over', ''); changed(); say(name(kb) + ' moved to ' + pos(kb) + '.'); };
  root.addEventListener('click', e => {
    const h = e.target.closest('[data-dd-handle]'); if (!h || dragged) return;
    const item = h.closest('[data-dd-item]');
    if (kb === item) return finish(name(item) + ' dropped, ' + pos(item) + '.');
    if (kb) finish(name(kb) + ' dropped, ' + pos(kb) + '.');
    kb = item; kbh = h; home = [item.parentElement, item.nextSibling];
    item.setAttribute('data-grabbed', ''); h.setAttribute('aria-pressed', 'true'); root.setAttribute('data-dd-active', ''); item.parentElement.setAttribute('data-over', ''); h.focus();
    say(name(item) + ' picked up, ' + pos(item) + '. Use the arrow keys to move it, Space to drop it, Escape to cancel.');
  });
  root.addEventListener('keydown', e => {
    if (!kb || e.target !== kbh) return;
    if (e.key === 'Escape') { e.preventDefault(); moving = true; putBack(kb); kbh.focus(); moving = false; return finish('Move cancelled. ' + name(kb) + ' returned to ' + pos(kb) + '.'); }
    const d = { ArrowUp: -1, ArrowLeft: -1, ArrowDown: 1, ArrowRight: 1 }[e.key]; if (!d) return;
    e.preventDefault();
    const l = kb.parentElement, all = items(l), i = all.indexOf(kb), ls = lists(l), side = e.key === 'ArrowLeft' || e.key === 'ArrowRight';
    if (side && ls.length > 1) {
      const t = ls[ls.indexOf(l) + d];
      if (!t) return say(name(kb) + ' is already in ' + l.getAttribute('aria-label') + ', the ' + (d < 0 ? 'first' : 'last') + ' list.');
      if (full(t)) return say(t.getAttribute('aria-label') + ' is full.');
      return move(() => t.insertBefore(kb, items(t)[i] || null));
    }
    const step = grid && !side ? d * all.filter(x => x.offsetTop === all[0].offsetTop).length : d, j = Math.max(0, Math.min(all.length - 1, i + step));
    if (j === i) return say(name(kb) + ' is already ' + (d < 0 ? 'first' : 'last') + '.');
    move(() => l.insertBefore(kb, j > i ? all[j].nextSibling : all[j]));
  });
  root.addEventListener('focusout', e => { if (kb && !moving && e.target === kbh) finish(name(kb) + ' dropped, ' + pos(kb) + '.'); });
  changed();
});

About this set

Two patterns that go beyond a flat list. Nested outline: sections and pages is a documentation sidebar with three sections, each holding its own pages. Drag a whole section to reorder sections, or drag a single page into another section; each section header counts its pages and an emptied section offers a Drop pages here slot. Sections and pages belong to separate groups, so a section can never be dropped inside another section or into its own pages. Priority ranking with numbers asks what matters most in a laptop and lists six criteria with rank numbers that are rewritten after every move, the top three highlighted. Each row also has Move up and Move down buttons, so anyone can rank with plain clicks or taps without dragging at all. Use the outline for navigation and menu builders, course outlines and file trees, and the ranking for surveys, feature voting and preference forms. From the keyboard, Up and Down move a section or page, and Left and Right move a page to the previous or next section.

What’s included

  • Two-level outline: sections and pages reorder separately
  • Pages move between sections with pointer or Left and Right keys
  • Page counts per section and an empty-section slot
  • Rank numbers rewritten after every move
  • Move up and Move down buttons as a no-drag alternative

Accessibility

Every handle is a toggle button named Move plus the item name, with aria-pressed and the visible instructions joined through aria-describedby. Space or Enter picks the item up, the arrow keys move it, Space or Enter drops it and Escape puts it back, and each step is announced in an assertive aria-live region, for example "Forms and validation moved to Reference, position 1 of 3.". The ranking also has Move up and Move down buttons that disable themselves at either end with aria-disabled. Handles show a visible focus outline, and the tilt of the dragged copy and any transitions switch off under prefers-reduced-motion.

Customise it

Change --tr-accent and --tr-tint on .tr, the .tr-pages indent for the tree, and the :nth-child(-n+3) rule to highlight more or fewer top ranks. Bootstrap uses .btn-group for the arrows; Tailwind uses the tr colours in @theme.