Open demo

CSS code

HTML
<div class="grid">
<div><span class="lbl">Role permissions with a select-all root</span>
<div class="ct-card">
  <div class="ct-head"><p class="ct-title" id="cta-title">Editor role</p><p class="ct-status" id="cta-status" role="status" data-template="{n} of {t} permissions granted"></p></div>
  <ul class="ct" id="cta-tree" aria-labelledby="cta-title" data-checktree data-status="cta-status">
    <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl ct-parent"><input type="checkbox" class="ct-cb"><span>All workspace permissions</span></label><span class="ct-tally" data-tally aria-hidden="true"></span></div>
      <ul class="ct-group" id="cta-g1">
        <li><div class="ct-row"><button type="button" class="ct-tog" aria-expanded="true" aria-controls="cta-g2" aria-label="Projects group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl ct-parent"><input type="checkbox" class="ct-cb"><span>Projects</span></label><span class="ct-tally" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group" id="cta-g2">
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" checked><span>View projects</span></label></div></li>
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" checked><span>Create projects</span></label></div></li>
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" checked><span>Edit any project</span></label></div></li>
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb"><span>Delete projects</span></label></div></li>
          </ul>
        </li>
        <li><div class="ct-row"><button type="button" class="ct-tog" aria-expanded="true" aria-controls="cta-g3" aria-label="Billing group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl ct-parent"><input type="checkbox" class="ct-cb"><span>Billing</span></label><span class="ct-tally" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group" id="cta-g3">
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" checked><span>View invoices</span></label></div></li>
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" checked><span>Download invoices</span></label></div></li>
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb"><span>Manage payment methods</span></label></div></li>
          </ul>
        </li>
        <li><div class="ct-row"><button type="button" class="ct-tog" aria-expanded="false" aria-controls="cta-g4" aria-label="Members group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl ct-parent"><input type="checkbox" class="ct-cb"><span>Members</span></label><span class="ct-tally" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group" id="cta-g4" hidden>
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" checked><span>Invite members</span></label></div></li>
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb"><span>Remove members</span></label></div></li>
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb"><span>Change roles</span></label></div></li>
          </ul>
        </li>
        <li><div class="ct-row"><button type="button" class="ct-tog" aria-expanded="true" aria-controls="cta-g5" aria-label="Integrations group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl ct-parent"><input type="checkbox" class="ct-cb"><span>Integrations</span></label><span class="ct-tally" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group" id="cta-g5">
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" checked><span>Post to Slack</span></label></div></li>
            <li><div class="ct-row"><button type="button" class="ct-tog" aria-expanded="true" aria-controls="cta-g6" aria-label="GitHub group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl ct-parent"><input type="checkbox" class="ct-cb"><span>GitHub</span></label><span class="ct-tally" data-tally aria-hidden="true"></span></div>
              <ul class="ct-group" id="cta-g6">
                <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" checked><span>Read repositories</span></label></div></li>
                <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb"><span>Open pull requests</span></label></div></li>
              </ul>
            </li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</div>
</div>
<div><span class="lbl">Shop filters with product counts</span>
<div class="ct-card">
  <div class="ct-head"><p class="ct-title" id="ctb-title">Filter by category</p><p class="ct-status" id="ctb-status" role="status" data-template="{sum} products match"></p></div>
  <ul class="ct" id="ctb-tree" aria-labelledby="ctb-title" data-checktree data-status="ctb-status">
    <li><div class="ct-row"><button type="button" class="ct-tog" aria-expanded="true" aria-controls="ctb-g1" aria-label="Clothing group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl ct-parent"><input type="checkbox" class="ct-cb"><span>Clothing</span></label><span class="ct-tally" data-tally aria-hidden="true"></span></div>
      <ul class="ct-group" id="ctb-g1">
        <li><div class="ct-row"><button type="button" class="ct-tog" aria-expanded="true" aria-controls="ctb-g2" aria-label="Jackets group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl ct-parent"><input type="checkbox" class="ct-cb"><span>Jackets</span></label><span class="ct-tally" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group" id="ctb-g2">
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" checked data-count="64"><span>Rain jackets</span><span class="ct-num">64<span class="sr-only"> products</span></span></label></div></li>
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" data-count="38"><span>Insulated jackets</span><span class="ct-num">38<span class="sr-only"> products</span></span></label></div></li>
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" checked data-count="51"><span>Fleece</span><span class="ct-num">51<span class="sr-only"> products</span></span></label></div></li>
          </ul>
        </li>
        <li><div class="ct-row"><button type="button" class="ct-tog" aria-expanded="false" aria-controls="ctb-g3" aria-label="Trousers group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl ct-parent"><input type="checkbox" class="ct-cb"><span>Trousers</span></label><span class="ct-tally" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group" id="ctb-g3" hidden>
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" data-count="47"><span>Hiking trousers</span><span class="ct-num">47<span class="sr-only"> products</span></span></label></div></li>
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" data-count="22"><span>Waterproof trousers</span><span class="ct-num">22<span class="sr-only"> products</span></span></label></div></li>
          </ul>
        </li>
      </ul>
    </li>
    <li><div class="ct-row"><button type="button" class="ct-tog" aria-expanded="true" aria-controls="ctb-g4" aria-label="Footwear group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl ct-parent"><input type="checkbox" class="ct-cb"><span>Footwear</span></label><span class="ct-tally" data-tally aria-hidden="true"></span></div>
      <ul class="ct-group" id="ctb-g4">
        <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" checked data-count="73"><span>Walking boots</span><span class="ct-num">73<span class="sr-only"> products</span></span></label></div></li>
        <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" data-count="58"><span>Trail runners</span><span class="ct-num">58<span class="sr-only"> products</span></span></label></div></li>
        <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" data-count="19"><span>Sandals</span><span class="ct-num">19<span class="sr-only"> products</span></span></label></div></li>
      </ul>
    </li>
    <li><div class="ct-row"><button type="button" class="ct-tog" aria-expanded="false" aria-controls="ctb-g5" aria-label="Equipment group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl ct-parent"><input type="checkbox" class="ct-cb"><span>Equipment</span></label><span class="ct-tally" data-tally aria-hidden="true"></span></div>
      <ul class="ct-group" id="ctb-g5" hidden>
        <li><div class="ct-row"><button type="button" class="ct-tog" aria-expanded="true" aria-controls="ctb-g6" aria-label="Tents group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl ct-parent"><input type="checkbox" class="ct-cb"><span>Tents</span></label><span class="ct-tally" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group" id="ctb-g6">
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" data-count="21"><span>2-person tents</span><span class="ct-num">21<span class="sr-only"> products</span></span></label></div></li>
            <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" data-count="12"><span>4-person tents</span><span class="ct-num">12<span class="sr-only"> products</span></span></label></div></li>
          </ul>
        </li>
        <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" data-count="66"><span>Backpacks</span><span class="ct-num">66<span class="sr-only"> products</span></span></label></div></li>
        <li><div class="ct-row"><span class="ct-sp"></span><label class="ct-lbl"><input type="checkbox" class="ct-cb" data-count="31"><span>Head torches</span><span class="ct-num">31<span class="sr-only"> products</span></span></label></div></li>
      </ul>
    </li>
  </ul>
  <div class="ct-foot"><button type="button" class="ct-btn ct-ghost" data-clear="ctb-tree">Clear all</button><button type="button" class="ct-btn ct-solid">Apply filters</button></div>
</div>
</div>
</div>
CSS
/* Checkbox tree: nested lists of real checkboxes. Parents are tri-state (JavaScript sets .indeterminate),
   groups collapse with a disclosure button (aria-expanded + hidden). */
.ct,.ct-card{--ct-accent:#4f46e5;--ct-text:#1f2937;--ct-muted:#5b6577;--ct-line:#e1e5ec;--ct-soft:#eef0fb}
.ct,.ct-group{margin:0;padding:0;list-style:none}
.ct{font-size:.9rem;color:var(--ct-text)}
.ct-group{margin-left:10px;padding-left:17px;border-left:1px solid var(--ct-line)}
.ct-row{display:flex;align-items:center;gap:8px;min-height:34px}
.ct-tog{display:inline-flex;align-items:center;justify-content:center;flex:none;width:22px;height:22px;padding:0;border:0;border-radius:6px;
  background:none;color:var(--ct-muted);cursor:pointer}
.ct-tog:hover{background:var(--ct-soft)}
.ct-tog:focus-visible{outline:2px solid var(--ct-accent);outline-offset:1px}
.ct-tog svg{transition:transform .15s ease}
.ct-tog[aria-expanded="true"] svg{transform:rotate(90deg)}
.ct-sp{width:22px;flex:none}
.ct-lbl{display:flex;align-items:center;gap:10px;flex:1;cursor:pointer}
.ct-parent{font-weight:600}
.ct-tally{margin-left:auto;padding:2px 8px;border-radius:99px;background:var(--ct-soft);color:#3730a3;font-size:.74rem;font-weight:600;font-variant-numeric:tabular-nums}
.ct-num{margin-left:auto;color:var(--ct-muted);font-size:.8rem;font-variant-numeric:tabular-nums}
.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 (prefers-reduced-motion:reduce){.ct-tog svg{transition:none}}
.ct-cb{--ct-accent:#4f46e5;appearance:none;-webkit-appearance:none;flex:none;box-sizing:border-box;width:18px;height:18px;margin:0;border:1.5px solid #7b8597;border-radius:5px;
  background:#fff center/12px 12px no-repeat;cursor:pointer;transition:background-color .15s,border-color .15s}
.ct-cb:checked{background-color:var(--ct-accent);border-color:var(--ct-accent);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.2l2.3 2.3 4.7-5' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")}
.ct-cb:indeterminate{background-color:var(--ct-accent);border-color:var(--ct-accent);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 6h6' stroke='%23fff' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E")}
.ct-cb:focus-visible{outline:2px solid var(--ct-accent);outline-offset:2px}
@media (prefers-reduced-motion:reduce){.ct-cb{transition:none}}
/* card around the demo */
.ct-card{background:#fff;border:1px solid #e2e7ee;border-radius:14px;overflow:hidden;box-shadow:0 10px 30px -22px rgba(15,23,42,.3)}
.ct-card>.ct{padding:12px 20px 14px}
.ct-head{padding:14px 20px;border-bottom:1px solid #e9edf3}
.ct-title{margin:0;font-size:.95rem;font-weight:700}
.ct-status{margin:2px 0 0;font-size:.8rem;color:var(--ct-muted)}
.ct-foot{display:flex;justify-content:flex-end;gap:8px;padding:12px 20px;border-top:1px solid #e9edf3}
.ct-btn{padding:8px 14px;border-radius:8px;font:inherit;font-size:.85rem;font-weight:600;cursor:pointer}
.ct-btn:focus-visible{outline:2px solid var(--ct-accent);outline-offset:2px}
.ct-ghost{border:1px solid #c9ced8;background:#fff;color:#334155}
.ct-ghost:hover{background:#f4f5f8}
.ct-solid{border:1px solid var(--ct-accent);background:var(--ct-accent);color:#fff}
.ct-solid:hover{background:#4338ca}
JavaScript
/* Checkbox tree: checking a parent sets every box below it; each parent then shows checked, unchecked or
   mixed (the native indeterminate state, announced as "mixed" / "half checked"). Disclosure buttons hide groups. */
document.querySelectorAll('[data-checktree]').forEach(tree => {
  const box = li => li.querySelector(':scope > div input[type="checkbox"]');
  const lis = [...tree.querySelectorAll('li')];
  const parents = lis.filter(li => li.querySelector(':scope > ul')).reverse(); // deepest first
  const leavesIn = el => [...el.querySelectorAll('li')].filter(li => !li.querySelector(':scope > ul')).map(box);
  const out = document.getElementById(tree.dataset.status);
  const sync = () => {
    parents.forEach(li => {
      const kids = [...li.querySelectorAll(':scope > ul > li')].map(box);
      const on = kids.filter(c => c.checked).length, b = box(li);
      b.checked = on === kids.length;
      b.indeterminate = !b.checked && (on > 0 || kids.some(c => c.indeterminate));
      const leaves = leavesIn(li), tally = li.querySelector(':scope > div [data-tally]');
      if (tally) tally.textContent = leaves.filter(c => c.checked).length + '/' + leaves.length;
    });
    if (!out) return;
    const all = leavesIn(tree), on = all.filter(c => c.checked);
    const total = list => list.reduce((s, c) => s + Number(c.dataset.count || 0), 0);
    out.textContent = out.dataset.template.replace('{n}', on.length).replace('{t}', all.length)
      .replace('{sum}', total(on.length ? on : all));
  };
  tree.addEventListener('change', e => {
    const li = e.target.closest('li');
    li.querySelectorAll('input[type="checkbox"]').forEach(c => { c.checked = e.target.checked; c.indeterminate = false; });
    sync();
  });
  tree.addEventListener('click', e => {
    const btn = e.target.closest('button[aria-controls]'); if (!btn) return;
    const open = btn.getAttribute('aria-expanded') !== 'true';
    btn.setAttribute('aria-expanded', String(open));
    document.getElementById(btn.getAttribute('aria-controls')).hidden = !open;
  });
  document.querySelectorAll('[data-clear="' + tree.id + '"]').forEach(clear => clear.addEventListener('click', () => { tree.querySelectorAll('input[type="checkbox"]').forEach(c => { c.checked = false; c.indeterminate = false; }); sync(); }));
  sync();
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div class="grid">
<div><span class="lbl">Role permissions with a select-all root</span>
<div class="card ct-card rounded-4">
  <div class="card-header ct-head bg-white"><p class="ct-title fw-bold mb-0" id="cta-title">Editor role</p><p class="ct-status text-body-secondary mb-0" id="cta-status" role="status" data-template="{n} of {t} permissions granted"></p></div>
  <div class="card-body ct-body">
  <ul class="ct list-unstyled mb-0" id="cta-tree" aria-labelledby="cta-title" data-checktree data-status="cta-status">
    <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0 fw-semibold"><input type="checkbox" class="form-check-input ct-cb"><span>All workspace permissions</span></label><span class="ct-tally badge rounded-pill" data-tally aria-hidden="true"></span></div>
      <ul class="ct-group list-unstyled mb-0" id="cta-g1">
        <li><div class="ct-row d-flex align-items-center gap-2"><button type="button" class="ct-tog d-inline-flex align-items-center justify-content-center border-0 bg-transparent p-0 rounded-2" aria-expanded="true" aria-controls="cta-g2" aria-label="Projects group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0 fw-semibold"><input type="checkbox" class="form-check-input ct-cb"><span>Projects</span></label><span class="ct-tally badge rounded-pill" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group list-unstyled mb-0" id="cta-g2">
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" checked><span>View projects</span></label></div></li>
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" checked><span>Create projects</span></label></div></li>
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" checked><span>Edit any project</span></label></div></li>
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb"><span>Delete projects</span></label></div></li>
          </ul>
        </li>
        <li><div class="ct-row d-flex align-items-center gap-2"><button type="button" class="ct-tog d-inline-flex align-items-center justify-content-center border-0 bg-transparent p-0 rounded-2" aria-expanded="true" aria-controls="cta-g3" aria-label="Billing group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0 fw-semibold"><input type="checkbox" class="form-check-input ct-cb"><span>Billing</span></label><span class="ct-tally badge rounded-pill" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group list-unstyled mb-0" id="cta-g3">
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" checked><span>View invoices</span></label></div></li>
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" checked><span>Download invoices</span></label></div></li>
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb"><span>Manage payment methods</span></label></div></li>
          </ul>
        </li>
        <li><div class="ct-row d-flex align-items-center gap-2"><button type="button" class="ct-tog d-inline-flex align-items-center justify-content-center border-0 bg-transparent p-0 rounded-2" aria-expanded="false" aria-controls="cta-g4" aria-label="Members group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0 fw-semibold"><input type="checkbox" class="form-check-input ct-cb"><span>Members</span></label><span class="ct-tally badge rounded-pill" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group list-unstyled mb-0" id="cta-g4" hidden>
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" checked><span>Invite members</span></label></div></li>
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb"><span>Remove members</span></label></div></li>
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb"><span>Change roles</span></label></div></li>
          </ul>
        </li>
        <li><div class="ct-row d-flex align-items-center gap-2"><button type="button" class="ct-tog d-inline-flex align-items-center justify-content-center border-0 bg-transparent p-0 rounded-2" aria-expanded="true" aria-controls="cta-g5" aria-label="Integrations group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0 fw-semibold"><input type="checkbox" class="form-check-input ct-cb"><span>Integrations</span></label><span class="ct-tally badge rounded-pill" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group list-unstyled mb-0" id="cta-g5">
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" checked><span>Post to Slack</span></label></div></li>
            <li><div class="ct-row d-flex align-items-center gap-2"><button type="button" class="ct-tog d-inline-flex align-items-center justify-content-center border-0 bg-transparent p-0 rounded-2" aria-expanded="true" aria-controls="cta-g6" aria-label="GitHub group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0 fw-semibold"><input type="checkbox" class="form-check-input ct-cb"><span>GitHub</span></label><span class="ct-tally badge rounded-pill" data-tally aria-hidden="true"></span></div>
              <ul class="ct-group list-unstyled mb-0" id="cta-g6">
                <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" checked><span>Read repositories</span></label></div></li>
                <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb"><span>Open pull requests</span></label></div></li>
              </ul>
            </li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
  </div>
</div>
</div>
<div><span class="lbl">Shop filters with product counts</span>
<div class="card ct-card rounded-4">
  <div class="card-header ct-head bg-white"><p class="ct-title fw-bold mb-0" id="ctb-title">Filter by category</p><p class="ct-status text-body-secondary mb-0" id="ctb-status" role="status" data-template="{sum} products match"></p></div>
  <div class="card-body ct-body">
  <ul class="ct list-unstyled mb-0" id="ctb-tree" aria-labelledby="ctb-title" data-checktree data-status="ctb-status">
    <li><div class="ct-row d-flex align-items-center gap-2"><button type="button" class="ct-tog d-inline-flex align-items-center justify-content-center border-0 bg-transparent p-0 rounded-2" aria-expanded="true" aria-controls="ctb-g1" aria-label="Clothing group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0 fw-semibold"><input type="checkbox" class="form-check-input ct-cb"><span>Clothing</span></label><span class="ct-tally badge rounded-pill" data-tally aria-hidden="true"></span></div>
      <ul class="ct-group list-unstyled mb-0" id="ctb-g1">
        <li><div class="ct-row d-flex align-items-center gap-2"><button type="button" class="ct-tog d-inline-flex align-items-center justify-content-center border-0 bg-transparent p-0 rounded-2" aria-expanded="true" aria-controls="ctb-g2" aria-label="Jackets group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0 fw-semibold"><input type="checkbox" class="form-check-input ct-cb"><span>Jackets</span></label><span class="ct-tally badge rounded-pill" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group list-unstyled mb-0" id="ctb-g2">
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" checked data-count="64"><span>Rain jackets</span><span class="ct-num ms-auto text-body-secondary">64<span class="visually-hidden"> products</span></span></label></div></li>
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" data-count="38"><span>Insulated jackets</span><span class="ct-num ms-auto text-body-secondary">38<span class="visually-hidden"> products</span></span></label></div></li>
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" checked data-count="51"><span>Fleece</span><span class="ct-num ms-auto text-body-secondary">51<span class="visually-hidden"> products</span></span></label></div></li>
          </ul>
        </li>
        <li><div class="ct-row d-flex align-items-center gap-2"><button type="button" class="ct-tog d-inline-flex align-items-center justify-content-center border-0 bg-transparent p-0 rounded-2" aria-expanded="false" aria-controls="ctb-g3" aria-label="Trousers group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0 fw-semibold"><input type="checkbox" class="form-check-input ct-cb"><span>Trousers</span></label><span class="ct-tally badge rounded-pill" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group list-unstyled mb-0" id="ctb-g3" hidden>
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" data-count="47"><span>Hiking trousers</span><span class="ct-num ms-auto text-body-secondary">47<span class="visually-hidden"> products</span></span></label></div></li>
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" data-count="22"><span>Waterproof trousers</span><span class="ct-num ms-auto text-body-secondary">22<span class="visually-hidden"> products</span></span></label></div></li>
          </ul>
        </li>
      </ul>
    </li>
    <li><div class="ct-row d-flex align-items-center gap-2"><button type="button" class="ct-tog d-inline-flex align-items-center justify-content-center border-0 bg-transparent p-0 rounded-2" aria-expanded="true" aria-controls="ctb-g4" aria-label="Footwear group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0 fw-semibold"><input type="checkbox" class="form-check-input ct-cb"><span>Footwear</span></label><span class="ct-tally badge rounded-pill" data-tally aria-hidden="true"></span></div>
      <ul class="ct-group list-unstyled mb-0" id="ctb-g4">
        <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" checked data-count="73"><span>Walking boots</span><span class="ct-num ms-auto text-body-secondary">73<span class="visually-hidden"> products</span></span></label></div></li>
        <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" data-count="58"><span>Trail runners</span><span class="ct-num ms-auto text-body-secondary">58<span class="visually-hidden"> products</span></span></label></div></li>
        <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" data-count="19"><span>Sandals</span><span class="ct-num ms-auto text-body-secondary">19<span class="visually-hidden"> products</span></span></label></div></li>
      </ul>
    </li>
    <li><div class="ct-row d-flex align-items-center gap-2"><button type="button" class="ct-tog d-inline-flex align-items-center justify-content-center border-0 bg-transparent p-0 rounded-2" aria-expanded="false" aria-controls="ctb-g5" aria-label="Equipment group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0 fw-semibold"><input type="checkbox" class="form-check-input ct-cb"><span>Equipment</span></label><span class="ct-tally badge rounded-pill" data-tally aria-hidden="true"></span></div>
      <ul class="ct-group list-unstyled mb-0" id="ctb-g5" hidden>
        <li><div class="ct-row d-flex align-items-center gap-2"><button type="button" class="ct-tog d-inline-flex align-items-center justify-content-center border-0 bg-transparent p-0 rounded-2" aria-expanded="true" aria-controls="ctb-g6" aria-label="Tents group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0 fw-semibold"><input type="checkbox" class="form-check-input ct-cb"><span>Tents</span></label><span class="ct-tally badge rounded-pill" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group list-unstyled mb-0" id="ctb-g6">
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" data-count="21"><span>2-person tents</span><span class="ct-num ms-auto text-body-secondary">21<span class="visually-hidden"> products</span></span></label></div></li>
            <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" data-count="12"><span>4-person tents</span><span class="ct-num ms-auto text-body-secondary">12<span class="visually-hidden"> products</span></span></label></div></li>
          </ul>
        </li>
        <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" data-count="66"><span>Backpacks</span><span class="ct-num ms-auto text-body-secondary">66<span class="visually-hidden"> products</span></span></label></div></li>
        <li><div class="ct-row d-flex align-items-center gap-2"><span class="ct-sp"></span><label class="ct-lbl d-flex align-items-center gap-2 flex-grow-1 mb-0"><input type="checkbox" class="form-check-input ct-cb" data-count="31"><span>Head torches</span><span class="ct-num ms-auto text-body-secondary">31<span class="visually-hidden"> products</span></span></label></div></li>
      </ul>
    </li>
  </ul>
  </div>
  <div class="card-footer ct-foot d-flex gap-2 justify-content-end bg-white"><button type="button" class="btn btn-outline-secondary ct-ghost" data-clear="ctb-tree">Clear all</button><button type="button" class="btn btn-primary ct-solid">Apply filters</button></div>
</div>
</div>
</div>
CSS
/* Checkbox tree on Bootstrap's .form-check-input, which already draws the indeterminate dash.
   Parents are tri-state (JavaScript sets .indeterminate); groups collapse with a disclosure button. */
.ct-card{--ct-accent:#4f46e5;--ct-muted:#5b6577}
.ct{--ct-accent:#4f46e5;--ct-text:#1f2937;--ct-muted:#5b6577;--ct-line:#e1e5ec;--ct-soft:#eef0fb;font-size:.9rem;color:var(--ct-text)}
.ct-group{margin-left:10px;padding-left:17px;border-left:1px solid var(--ct-line)}
.ct-row{min-height:34px}
.ct-tog{flex:none;width:22px;height:22px;color:var(--ct-muted)}
.ct-tog:hover{background:var(--ct-soft)}
.ct-tog:focus-visible{outline:2px solid var(--ct-accent);outline-offset:1px}
.ct-tog svg{transition:transform .15s ease}
.ct-tog[aria-expanded="true"] svg{transform:rotate(90deg)}
.ct-sp{width:22px;flex:none}
.ct-lbl{gap:10px!important;cursor:pointer}
.ct .form-check-input{flex:none;width:18px;height:18px;margin:0;border:1.5px solid #7b8597;border-radius:5px;cursor:pointer}
.ct .form-check-input:checked,.ct .form-check-input:indeterminate{background-color:var(--ct-accent);border-color:var(--ct-accent)}
.ct .form-check-input:focus{box-shadow:none}
.ct .form-check-input:focus-visible{outline:2px solid var(--ct-accent);outline-offset:2px}
.ct-tally{--bs-badge-padding-x:8px;--bs-badge-padding-y:3px;--bs-badge-font-size:.74rem;--bs-badge-color:#3730a3;margin-left:auto;background:var(--ct-soft)}
.ct-num{font-size:.8rem;font-variant-numeric:tabular-nums}
@media (prefers-reduced-motion:reduce){.ct-tog svg,.ct .form-check-input{transition:none}}
.ct-card{--bs-card-border-color:#e2e7ee;box-shadow:0 10px 30px -22px rgba(15,23,42,.3)}
.ct-body{--bs-card-spacer-y:12px;--bs-card-spacer-x:20px;padding-bottom:14px}
.ct-head{--bs-card-cap-padding-y:14px;--bs-card-cap-padding-x:20px;border-color:#e9edf3}
.ct-title{font-size:.95rem}
.ct-status{margin-top:2px;font-size:.8rem}
.ct-foot{--bs-card-cap-padding-y:12px;--bs-card-cap-padding-x:20px;border-color:#e9edf3}
.ct-foot .btn{--bs-btn-padding-x:14px;--bs-btn-padding-y:8px;--bs-btn-font-size:.85rem;--bs-btn-font-weight:600;--bs-btn-border-radius:8px;line-height:normal}
.ct-ghost{--bs-btn-color:#334155;--bs-btn-border-color:#c9ced8;--bs-btn-hover-color:#334155;--bs-btn-hover-bg:#f4f5f8;--bs-btn-hover-border-color:#c9ced8}
.ct-solid{--bs-btn-bg:var(--ct-accent);--bs-btn-border-color:var(--ct-accent);--bs-btn-hover-bg:#4338ca;--bs-btn-hover-border-color:#4338ca}
.ct-foot .btn:focus-visible{box-shadow:none;outline:2px solid var(--ct-accent);outline-offset:2px}
JavaScript
/* Checkbox tree: checking a parent sets every box below it; each parent then shows checked, unchecked or
   mixed (the native indeterminate state, announced as "mixed" / "half checked"). Disclosure buttons hide groups. */
document.querySelectorAll('[data-checktree]').forEach(tree => {
  const box = li => li.querySelector(':scope > div input[type="checkbox"]');
  const lis = [...tree.querySelectorAll('li')];
  const parents = lis.filter(li => li.querySelector(':scope > ul')).reverse(); // deepest first
  const leavesIn = el => [...el.querySelectorAll('li')].filter(li => !li.querySelector(':scope > ul')).map(box);
  const out = document.getElementById(tree.dataset.status);
  const sync = () => {
    parents.forEach(li => {
      const kids = [...li.querySelectorAll(':scope > ul > li')].map(box);
      const on = kids.filter(c => c.checked).length, b = box(li);
      b.checked = on === kids.length;
      b.indeterminate = !b.checked && (on > 0 || kids.some(c => c.indeterminate));
      const leaves = leavesIn(li), tally = li.querySelector(':scope > div [data-tally]');
      if (tally) tally.textContent = leaves.filter(c => c.checked).length + '/' + leaves.length;
    });
    if (!out) return;
    const all = leavesIn(tree), on = all.filter(c => c.checked);
    const total = list => list.reduce((s, c) => s + Number(c.dataset.count || 0), 0);
    out.textContent = out.dataset.template.replace('{n}', on.length).replace('{t}', all.length)
      .replace('{sum}', total(on.length ? on : all));
  };
  tree.addEventListener('change', e => {
    const li = e.target.closest('li');
    li.querySelectorAll('input[type="checkbox"]').forEach(c => { c.checked = e.target.checked; c.indeterminate = false; });
    sync();
  });
  tree.addEventListener('click', e => {
    const btn = e.target.closest('button[aria-controls]'); if (!btn) return;
    const open = btn.getAttribute('aria-expanded') !== 'true';
    btn.setAttribute('aria-expanded', String(open));
    document.getElementById(btn.getAttribute('aria-controls')).hidden = !open;
  });
  document.querySelectorAll('[data-clear="' + tree.id + '"]').forEach(clear => clear.addEventListener('click', () => { tree.querySelectorAll('input[type="checkbox"]').forEach(c => { c.checked = false; c.indeterminate = false; }); sync(); }));
  sync();
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div class="grid grid-cols-[repeat(auto-fit,minmax(min(100%,300px),1fr))] items-start gap-7">
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#5b6577] uppercase">Role permissions with a select-all root</span>
<div class="overflow-hidden rounded-[14px] border border-[#e2e7ee] bg-white shadow-[0_10px_30px_-22px_rgba(15,23,42,.3)]">
  <div class="border-b border-[#e9edf3] px-5 py-3.5"><p class="m-0 text-[.95rem] font-bold" id="cta-title">Editor role</p><p class="m-0 mt-0.5 text-[.8rem] text-[#5b6577]" id="cta-status" role="status" data-template="{n} of {t} permissions granted"></p></div>
  <ul class="ct m-0 list-none px-5 pt-3 pb-3.5 text-[.9rem] text-[#1f2937]" id="cta-tree" aria-labelledby="cta-title" data-checktree data-status="cta-status">
    <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5 font-semibold"><input type="checkbox" class="ct-cb"><span>All workspace permissions</span></label><span class="ml-auto rounded-full bg-[#eef0fb] px-2 py-0.5 text-[.74rem] font-semibold text-[#3730a3] tabular-nums" data-tally aria-hidden="true"></span></div>
      <ul class="ct-group m-0 ml-[10px] list-none border-l border-[#e1e5ec] p-0 pl-[17px]" id="cta-g1">
        <li><div class="flex min-h-[34px] items-center gap-2"><button type="button" class="inline-flex size-[22px] flex-none cursor-pointer items-center justify-content-center justify-center rounded-md border-0 bg-transparent p-0 text-[#5b6577] hover:bg-[#eef0fb] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[#4f46e5] [&_svg]:transition-transform [&_svg]:duration-150 motion-reduce:[&_svg]:transition-none [&[aria-expanded=true]_svg]:rotate-90" aria-expanded="true" aria-controls="cta-g2" aria-label="Projects group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="flex flex-1 cursor-pointer items-center gap-2.5 font-semibold"><input type="checkbox" class="ct-cb"><span>Projects</span></label><span class="ml-auto rounded-full bg-[#eef0fb] px-2 py-0.5 text-[.74rem] font-semibold text-[#3730a3] tabular-nums" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group m-0 ml-[10px] list-none border-l border-[#e1e5ec] p-0 pl-[17px]" id="cta-g2">
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" checked><span>View projects</span></label></div></li>
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" checked><span>Create projects</span></label></div></li>
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" checked><span>Edit any project</span></label></div></li>
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb"><span>Delete projects</span></label></div></li>
          </ul>
        </li>
        <li><div class="flex min-h-[34px] items-center gap-2"><button type="button" class="inline-flex size-[22px] flex-none cursor-pointer items-center justify-content-center justify-center rounded-md border-0 bg-transparent p-0 text-[#5b6577] hover:bg-[#eef0fb] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[#4f46e5] [&_svg]:transition-transform [&_svg]:duration-150 motion-reduce:[&_svg]:transition-none [&[aria-expanded=true]_svg]:rotate-90" aria-expanded="true" aria-controls="cta-g3" aria-label="Billing group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="flex flex-1 cursor-pointer items-center gap-2.5 font-semibold"><input type="checkbox" class="ct-cb"><span>Billing</span></label><span class="ml-auto rounded-full bg-[#eef0fb] px-2 py-0.5 text-[.74rem] font-semibold text-[#3730a3] tabular-nums" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group m-0 ml-[10px] list-none border-l border-[#e1e5ec] p-0 pl-[17px]" id="cta-g3">
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" checked><span>View invoices</span></label></div></li>
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" checked><span>Download invoices</span></label></div></li>
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb"><span>Manage payment methods</span></label></div></li>
          </ul>
        </li>
        <li><div class="flex min-h-[34px] items-center gap-2"><button type="button" class="inline-flex size-[22px] flex-none cursor-pointer items-center justify-content-center justify-center rounded-md border-0 bg-transparent p-0 text-[#5b6577] hover:bg-[#eef0fb] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[#4f46e5] [&_svg]:transition-transform [&_svg]:duration-150 motion-reduce:[&_svg]:transition-none [&[aria-expanded=true]_svg]:rotate-90" aria-expanded="false" aria-controls="cta-g4" aria-label="Members group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="flex flex-1 cursor-pointer items-center gap-2.5 font-semibold"><input type="checkbox" class="ct-cb"><span>Members</span></label><span class="ml-auto rounded-full bg-[#eef0fb] px-2 py-0.5 text-[.74rem] font-semibold text-[#3730a3] tabular-nums" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group m-0 ml-[10px] list-none border-l border-[#e1e5ec] p-0 pl-[17px]" id="cta-g4" hidden>
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" checked><span>Invite members</span></label></div></li>
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb"><span>Remove members</span></label></div></li>
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb"><span>Change roles</span></label></div></li>
          </ul>
        </li>
        <li><div class="flex min-h-[34px] items-center gap-2"><button type="button" class="inline-flex size-[22px] flex-none cursor-pointer items-center justify-content-center justify-center rounded-md border-0 bg-transparent p-0 text-[#5b6577] hover:bg-[#eef0fb] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[#4f46e5] [&_svg]:transition-transform [&_svg]:duration-150 motion-reduce:[&_svg]:transition-none [&[aria-expanded=true]_svg]:rotate-90" aria-expanded="true" aria-controls="cta-g5" aria-label="Integrations group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="flex flex-1 cursor-pointer items-center gap-2.5 font-semibold"><input type="checkbox" class="ct-cb"><span>Integrations</span></label><span class="ml-auto rounded-full bg-[#eef0fb] px-2 py-0.5 text-[.74rem] font-semibold text-[#3730a3] tabular-nums" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group m-0 ml-[10px] list-none border-l border-[#e1e5ec] p-0 pl-[17px]" id="cta-g5">
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" checked><span>Post to Slack</span></label></div></li>
            <li><div class="flex min-h-[34px] items-center gap-2"><button type="button" class="inline-flex size-[22px] flex-none cursor-pointer items-center justify-content-center justify-center rounded-md border-0 bg-transparent p-0 text-[#5b6577] hover:bg-[#eef0fb] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[#4f46e5] [&_svg]:transition-transform [&_svg]:duration-150 motion-reduce:[&_svg]:transition-none [&[aria-expanded=true]_svg]:rotate-90" aria-expanded="true" aria-controls="cta-g6" aria-label="GitHub group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="flex flex-1 cursor-pointer items-center gap-2.5 font-semibold"><input type="checkbox" class="ct-cb"><span>GitHub</span></label><span class="ml-auto rounded-full bg-[#eef0fb] px-2 py-0.5 text-[.74rem] font-semibold text-[#3730a3] tabular-nums" data-tally aria-hidden="true"></span></div>
              <ul class="ct-group m-0 ml-[10px] list-none border-l border-[#e1e5ec] p-0 pl-[17px]" id="cta-g6">
                <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" checked><span>Read repositories</span></label></div></li>
                <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb"><span>Open pull requests</span></label></div></li>
              </ul>
            </li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</div>
</div>
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#5b6577] uppercase">Shop filters with product counts</span>
<div class="overflow-hidden rounded-[14px] border border-[#e2e7ee] bg-white shadow-[0_10px_30px_-22px_rgba(15,23,42,.3)]">
  <div class="border-b border-[#e9edf3] px-5 py-3.5"><p class="m-0 text-[.95rem] font-bold" id="ctb-title">Filter by category</p><p class="m-0 mt-0.5 text-[.8rem] text-[#5b6577]" id="ctb-status" role="status" data-template="{sum} products match"></p></div>
  <ul class="ct m-0 list-none px-5 pt-3 pb-3.5 text-[.9rem] text-[#1f2937]" id="ctb-tree" aria-labelledby="ctb-title" data-checktree data-status="ctb-status">
    <li><div class="flex min-h-[34px] items-center gap-2"><button type="button" class="inline-flex size-[22px] flex-none cursor-pointer items-center justify-content-center justify-center rounded-md border-0 bg-transparent p-0 text-[#5b6577] hover:bg-[#eef0fb] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[#4f46e5] [&_svg]:transition-transform [&_svg]:duration-150 motion-reduce:[&_svg]:transition-none [&[aria-expanded=true]_svg]:rotate-90" aria-expanded="true" aria-controls="ctb-g1" aria-label="Clothing group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="flex flex-1 cursor-pointer items-center gap-2.5 font-semibold"><input type="checkbox" class="ct-cb"><span>Clothing</span></label><span class="ml-auto rounded-full bg-[#eef0fb] px-2 py-0.5 text-[.74rem] font-semibold text-[#3730a3] tabular-nums" data-tally aria-hidden="true"></span></div>
      <ul class="ct-group m-0 ml-[10px] list-none border-l border-[#e1e5ec] p-0 pl-[17px]" id="ctb-g1">
        <li><div class="flex min-h-[34px] items-center gap-2"><button type="button" class="inline-flex size-[22px] flex-none cursor-pointer items-center justify-content-center justify-center rounded-md border-0 bg-transparent p-0 text-[#5b6577] hover:bg-[#eef0fb] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[#4f46e5] [&_svg]:transition-transform [&_svg]:duration-150 motion-reduce:[&_svg]:transition-none [&[aria-expanded=true]_svg]:rotate-90" aria-expanded="true" aria-controls="ctb-g2" aria-label="Jackets group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="flex flex-1 cursor-pointer items-center gap-2.5 font-semibold"><input type="checkbox" class="ct-cb"><span>Jackets</span></label><span class="ml-auto rounded-full bg-[#eef0fb] px-2 py-0.5 text-[.74rem] font-semibold text-[#3730a3] tabular-nums" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group m-0 ml-[10px] list-none border-l border-[#e1e5ec] p-0 pl-[17px]" id="ctb-g2">
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" checked data-count="64"><span>Rain jackets</span><span class="ml-auto text-[.8rem] text-[#5b6577] tabular-nums">64<span class="sr-only"> products</span></span></label></div></li>
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" data-count="38"><span>Insulated jackets</span><span class="ml-auto text-[.8rem] text-[#5b6577] tabular-nums">38<span class="sr-only"> products</span></span></label></div></li>
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" checked data-count="51"><span>Fleece</span><span class="ml-auto text-[.8rem] text-[#5b6577] tabular-nums">51<span class="sr-only"> products</span></span></label></div></li>
          </ul>
        </li>
        <li><div class="flex min-h-[34px] items-center gap-2"><button type="button" class="inline-flex size-[22px] flex-none cursor-pointer items-center justify-content-center justify-center rounded-md border-0 bg-transparent p-0 text-[#5b6577] hover:bg-[#eef0fb] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[#4f46e5] [&_svg]:transition-transform [&_svg]:duration-150 motion-reduce:[&_svg]:transition-none [&[aria-expanded=true]_svg]:rotate-90" aria-expanded="false" aria-controls="ctb-g3" aria-label="Trousers group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="flex flex-1 cursor-pointer items-center gap-2.5 font-semibold"><input type="checkbox" class="ct-cb"><span>Trousers</span></label><span class="ml-auto rounded-full bg-[#eef0fb] px-2 py-0.5 text-[.74rem] font-semibold text-[#3730a3] tabular-nums" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group m-0 ml-[10px] list-none border-l border-[#e1e5ec] p-0 pl-[17px]" id="ctb-g3" hidden>
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" data-count="47"><span>Hiking trousers</span><span class="ml-auto text-[.8rem] text-[#5b6577] tabular-nums">47<span class="sr-only"> products</span></span></label></div></li>
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" data-count="22"><span>Waterproof trousers</span><span class="ml-auto text-[.8rem] text-[#5b6577] tabular-nums">22<span class="sr-only"> products</span></span></label></div></li>
          </ul>
        </li>
      </ul>
    </li>
    <li><div class="flex min-h-[34px] items-center gap-2"><button type="button" class="inline-flex size-[22px] flex-none cursor-pointer items-center justify-content-center justify-center rounded-md border-0 bg-transparent p-0 text-[#5b6577] hover:bg-[#eef0fb] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[#4f46e5] [&_svg]:transition-transform [&_svg]:duration-150 motion-reduce:[&_svg]:transition-none [&[aria-expanded=true]_svg]:rotate-90" aria-expanded="true" aria-controls="ctb-g4" aria-label="Footwear group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="flex flex-1 cursor-pointer items-center gap-2.5 font-semibold"><input type="checkbox" class="ct-cb"><span>Footwear</span></label><span class="ml-auto rounded-full bg-[#eef0fb] px-2 py-0.5 text-[.74rem] font-semibold text-[#3730a3] tabular-nums" data-tally aria-hidden="true"></span></div>
      <ul class="ct-group m-0 ml-[10px] list-none border-l border-[#e1e5ec] p-0 pl-[17px]" id="ctb-g4">
        <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" checked data-count="73"><span>Walking boots</span><span class="ml-auto text-[.8rem] text-[#5b6577] tabular-nums">73<span class="sr-only"> products</span></span></label></div></li>
        <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" data-count="58"><span>Trail runners</span><span class="ml-auto text-[.8rem] text-[#5b6577] tabular-nums">58<span class="sr-only"> products</span></span></label></div></li>
        <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" data-count="19"><span>Sandals</span><span class="ml-auto text-[.8rem] text-[#5b6577] tabular-nums">19<span class="sr-only"> products</span></span></label></div></li>
      </ul>
    </li>
    <li><div class="flex min-h-[34px] items-center gap-2"><button type="button" class="inline-flex size-[22px] flex-none cursor-pointer items-center justify-content-center justify-center rounded-md border-0 bg-transparent p-0 text-[#5b6577] hover:bg-[#eef0fb] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[#4f46e5] [&_svg]:transition-transform [&_svg]:duration-150 motion-reduce:[&_svg]:transition-none [&[aria-expanded=true]_svg]:rotate-90" aria-expanded="false" aria-controls="ctb-g5" aria-label="Equipment group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="flex flex-1 cursor-pointer items-center gap-2.5 font-semibold"><input type="checkbox" class="ct-cb"><span>Equipment</span></label><span class="ml-auto rounded-full bg-[#eef0fb] px-2 py-0.5 text-[.74rem] font-semibold text-[#3730a3] tabular-nums" data-tally aria-hidden="true"></span></div>
      <ul class="ct-group m-0 ml-[10px] list-none border-l border-[#e1e5ec] p-0 pl-[17px]" id="ctb-g5" hidden>
        <li><div class="flex min-h-[34px] items-center gap-2"><button type="button" class="inline-flex size-[22px] flex-none cursor-pointer items-center justify-content-center justify-center rounded-md border-0 bg-transparent p-0 text-[#5b6577] hover:bg-[#eef0fb] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[#4f46e5] [&_svg]:transition-transform [&_svg]:duration-150 motion-reduce:[&_svg]:transition-none [&[aria-expanded=true]_svg]:rotate-90" aria-expanded="true" aria-controls="ctb-g6" aria-label="Tents group"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" focusable="false"><path d="M9 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button><label class="flex flex-1 cursor-pointer items-center gap-2.5 font-semibold"><input type="checkbox" class="ct-cb"><span>Tents</span></label><span class="ml-auto rounded-full bg-[#eef0fb] px-2 py-0.5 text-[.74rem] font-semibold text-[#3730a3] tabular-nums" data-tally aria-hidden="true"></span></div>
          <ul class="ct-group m-0 ml-[10px] list-none border-l border-[#e1e5ec] p-0 pl-[17px]" id="ctb-g6">
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" data-count="21"><span>2-person tents</span><span class="ml-auto text-[.8rem] text-[#5b6577] tabular-nums">21<span class="sr-only"> products</span></span></label></div></li>
            <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" data-count="12"><span>4-person tents</span><span class="ml-auto text-[.8rem] text-[#5b6577] tabular-nums">12<span class="sr-only"> products</span></span></label></div></li>
          </ul>
        </li>
        <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" data-count="66"><span>Backpacks</span><span class="ml-auto text-[.8rem] text-[#5b6577] tabular-nums">66<span class="sr-only"> products</span></span></label></div></li>
        <li><div class="flex min-h-[34px] items-center gap-2"><span class="w-[22px] flex-none"></span><label class="flex flex-1 cursor-pointer items-center gap-2.5"><input type="checkbox" class="ct-cb" data-count="31"><span>Head torches</span><span class="ml-auto text-[.8rem] text-[#5b6577] tabular-nums">31<span class="sr-only"> products</span></span></label></div></li>
      </ul>
    </li>
  </ul>
  <div class="flex justify-end gap-2 border-t border-[#e9edf3] px-5 py-3"><button type="button" class="cursor-pointer rounded-lg border border-[#c9ced8] bg-white px-3.5 py-2 text-[.85rem] font-semibold text-[#334155] hover:bg-[#f4f5f8] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]" data-clear="ctb-tree">Clear all</button><button type="button" class="cursor-pointer rounded-lg border border-[#4f46e5] bg-[#4f46e5] px-3.5 py-2 text-[.85rem] font-semibold text-white hover:bg-[#4338ca] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#4f46e5]">Apply filters</button></div>
</div>
</div>
</div>
CSS
/* Native checkbox restyled once here; the tri-state comes from the browser's :indeterminate. */
.ct-cb{--ct-accent:#4f46e5;appearance:none;-webkit-appearance:none;flex:none;box-sizing:border-box;width:18px;height:18px;margin:0;border:1.5px solid #7b8597;border-radius:5px;
  background:#fff center/12px 12px no-repeat;cursor:pointer;transition:background-color .15s,border-color .15s}
.ct-cb:checked{background-color:var(--ct-accent);border-color:var(--ct-accent);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.2l2.3 2.3 4.7-5' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")}
.ct-cb:indeterminate{background-color:var(--ct-accent);border-color:var(--ct-accent);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 6h6' stroke='%23fff' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E")}
.ct-cb:focus-visible{outline:2px solid var(--ct-accent);outline-offset:2px}
@media (prefers-reduced-motion:reduce){.ct-cb{transition:none}}
JavaScript
/* Checkbox tree: checking a parent sets every box below it; each parent then shows checked, unchecked or
   mixed (the native indeterminate state, announced as "mixed" / "half checked"). Disclosure buttons hide groups. */
document.querySelectorAll('[data-checktree]').forEach(tree => {
  const box = li => li.querySelector(':scope > div input[type="checkbox"]');
  const lis = [...tree.querySelectorAll('li')];
  const parents = lis.filter(li => li.querySelector(':scope > ul')).reverse(); // deepest first
  const leavesIn = el => [...el.querySelectorAll('li')].filter(li => !li.querySelector(':scope > ul')).map(box);
  const out = document.getElementById(tree.dataset.status);
  const sync = () => {
    parents.forEach(li => {
      const kids = [...li.querySelectorAll(':scope > ul > li')].map(box);
      const on = kids.filter(c => c.checked).length, b = box(li);
      b.checked = on === kids.length;
      b.indeterminate = !b.checked && (on > 0 || kids.some(c => c.indeterminate));
      const leaves = leavesIn(li), tally = li.querySelector(':scope > div [data-tally]');
      if (tally) tally.textContent = leaves.filter(c => c.checked).length + '/' + leaves.length;
    });
    if (!out) return;
    const all = leavesIn(tree), on = all.filter(c => c.checked);
    const total = list => list.reduce((s, c) => s + Number(c.dataset.count || 0), 0);
    out.textContent = out.dataset.template.replace('{n}', on.length).replace('{t}', all.length)
      .replace('{sum}', total(on.length ? on : all));
  };
  tree.addEventListener('change', e => {
    const li = e.target.closest('li');
    li.querySelectorAll('input[type="checkbox"]').forEach(c => { c.checked = e.target.checked; c.indeterminate = false; });
    sync();
  });
  tree.addEventListener('click', e => {
    const btn = e.target.closest('button[aria-controls]'); if (!btn) return;
    const open = btn.getAttribute('aria-expanded') !== 'true';
    btn.setAttribute('aria-expanded', String(open));
    document.getElementById(btn.getAttribute('aria-controls')).hidden = !open;
  });
  document.querySelectorAll('[data-clear="' + tree.id + '"]').forEach(clear => clear.addEventListener('click', () => { tree.querySelectorAll('input[type="checkbox"]').forEach(c => { c.checked = false; c.indeterminate = false; }); sync(); }));
  sync();
});

About this set

Two checkbox trees whose parent boxes are tri-state. Role permissions with a select-all root lists an editor role’s rights in four groups plus a nested GitHub group, with a checked-of-total tally on every parent and a summary line under the title. Shop filters with product counts is an outdoor store’s category filter with a product count on each leaf, a live total of matching products, a Clear all button and an Apply filters button. Each item is a real checkbox inside a label. When a parent changes, a short script copies its state to every box below it, then walks the parents from the deepest up: all children checked makes the parent checked, none makes it unchecked, and anything in between sets the native indeterminate property, which screen readers announce as mixed or half checked. Groups fold away with a separate disclosure button that has aria-expanded and toggles the hidden attribute, so clicking the label never collapses a branch by accident. The plain CSS and Tailwind versions draw the box, tick and dash with appearance none; the Bootstrap version uses .form-check-input, which already draws the indeterminate dash.

What’s included

  • Tri-state parents using the native indeterminate state
  • Checking a parent sets every descendant, children update every ancestor
  • Checked-of-total tally on each parent and a polite status line
  • Product counts, a running total and Clear all in the filter version
  • Separate disclosure buttons with aria-expanded for each group

Accessibility

Every checkbox is labelled by its visible text, and parents expose the mixed state through the native indeterminate property. The expand buttons are named after their group and report aria-expanded. The summary line is a role status region, so totals are announced politely. Checkboxes and buttons show a 2px indigo outline on keyboard focus, and transitions stop under prefers-reduced-motion.

Customise it

Change --ct-accent on .ct-cb and .ct for the checked colour, and --ct-line, --ct-soft and --ct-muted for guides, tallies and secondary text. In Bootstrap the accent is applied to .form-check-input:checked and :indeterminate; in Tailwind edit the colour utilities on the tally and buttons.