Open demo

CSS code

HTML
<div><span class="lbl">Dark to-do app</span>
<section class="dk" data-todo="studio" aria-labelledby="studio-title">
  <div class="dk-head"><div><h2 id="studio-title">Studio, this week</h2><p class="dk-count" data-todo-count>3 tasks left</p></div>
    <div class="dk-filters" role="group" aria-label="Show tasks"><button type="button" data-filter="all" aria-pressed="true">All</button><button type="button" data-filter="active" aria-pressed="false">Active</button><button type="button" data-filter="done" aria-pressed="false">Done</button></div></div>
  <form class="dk-form" data-todo-form>
    <label class="sr-only" for="studio-new">New task</label>
    <span class="dk-plus"><svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M8 3v10M3 8h10"/></svg></span><input id="studio-new" type="text" placeholder="Add a task" autocomplete="off" maxlength="120">
    <button class="dk-add" type="submit">Add</button>
  </form>
  <ul class="dk-list" data-todo-list aria-label="Studio tasks">
    <li class="dk-item"><input class="dk-box" type="checkbox" id="studio-task-1" checked><label for="studio-task-1" data-text>Export the logo set as SVG</label><button class="dk-del" type="button" data-delete aria-label="Delete “Export the logo set as SVG”"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li>
    <li class="dk-item"><input class="dk-box" type="checkbox" id="studio-task-2"><label for="studio-task-2" data-text>Send the moodboard to Halden & Co</label><button class="dk-del" type="button" data-delete aria-label="Delete “Send the moodboard to Halden & Co”"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li>
    <li class="dk-item"><input class="dk-box" type="checkbox" id="studio-task-3"><label for="studio-task-3" data-text>Fix the hover colour on the pricing cards</label><button class="dk-del" type="button" data-delete aria-label="Delete “Fix the hover colour on the pricing cards”"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li>
    <li class="dk-item"><input class="dk-box" type="checkbox" id="studio-task-4"><label for="studio-task-4" data-text>Book the meeting room for Friday's review</label><button class="dk-del" type="button" data-delete aria-label="Delete “Book the meeting room for Friday's review”"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li>
  </ul>
  <p class="dk-empty" data-todo-empty hidden></p>
  <div class="dk-foot"><p>Saved in this browser</p><button class="dk-clear" type="button" data-todo-clear>Clear done</button></div>
  <p class="sr-only" role="status" data-todo-live></p>
  <template data-todo-tpl><li class="dk-item"><input class="dk-box" type="checkbox"><label data-text></label><button class="dk-del" type="button" data-delete><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li></template>
</section></div>

<div><span class="lbl">Top three for today</span>
<section class="dk" aria-labelledby="top-title">
  <h2 id="top-title">Today’s top three</h2>
  <p class="dk-tsub">Do these before anything else. Everything else can wait.</p>
  <ol class="dk-top">
    <li class="dk-titem"><span class="dk-num" aria-hidden="true">1</span><input class="dk-box" type="checkbox" id="top-1" checked><label for="top-1"><span class="dk-tname">Finish the onboarding flow mockups</span><span class="dk-tnote">Due before the 15:00 review</span></label></li>
    <li class="dk-titem"><span class="dk-num" aria-hidden="true">2</span><input class="dk-box" type="checkbox" id="top-2"><label for="top-2"><span class="dk-tname">Reply to Dana about the API limits</span><span class="dk-tnote">She is waiting on a number</span></label></li>
    <li class="dk-titem"><span class="dk-num" aria-hidden="true">3</span><input class="dk-box" type="checkbox" id="top-3"><label for="top-3"><span class="dk-tname">Record the five-minute demo video</span><span class="dk-tnote">Use the staging account</span></label></li>
  </ol>
  <section class="dk-later" aria-labelledby="later-title">
    <h3 id="later-title">Later</h3>
    <ul class="dk-llist">
      <li class="dk-litem"><input class="dk-box" type="checkbox" id="later-1"><label for="later-1">Order a new monitor arm</label></li>
      <li class="dk-litem"><input class="dk-box" type="checkbox" id="later-2"><label for="later-2">Read the accessibility audit</label></li>
      <li class="dk-litem"><input class="dk-box" type="checkbox" id="later-3" checked><label for="later-3">Clear out the shared Downloads folder</label></li>
    </ul>
  </section>
</section></div>
CSS
/* Dark to-do lists. The app is the same script as the classic set (add, tick off, delete, filter, count,
   localStorage); colours are tuned for a near-black ground and checked for contrast. The top-three list needs no script. */
.dk{--dk-bg:#141821;--dk-raise:#1b202b;--dk-line:#262d3b;--dk-edge:#3a4356;--dk-ink:#e8eaf0;--dk-muted:#9aa3b5;--dk-box:#7a849a;
  --dk-accent:#a3e635;--dk-accent-ink:#1a2e05;--dk-accent-text:#bef264;--dk-danger:#fca5a5;--dk-danger-bg:#3a1d22;
  background:var(--dk-bg);color:var(--dk-ink);border:1px solid var(--dk-line);border-radius:20px;padding:24px;box-shadow:0 30px 60px -30px #000}
.dk [hidden]{display:none!important}
.dk :where(h2,h3,p,ul,ol){margin:0;padding:0}
.dk :where(ul,ol){list-style:none}
.dk h2{font-size:1.25rem;font-weight:800;letter-spacing:-.015em}
.dk-head{display:flex;align-items:center;justify-content:space-between;gap:12px 16px;flex-wrap:wrap}
.dk-count{margin-top:3px!important;font-size:.85rem;color:var(--dk-muted)}
.dk-filters{display:flex;gap:2px;padding:3px;border:1px solid var(--dk-line);border-radius:10px;background:#0f1218}
.dk-filters button{font:inherit;font-size:.82rem;font-weight:650;padding:6px 12px;border:0;border-radius:7px;background:transparent;color:var(--dk-muted);cursor:pointer}
.dk-filters button:hover{color:var(--dk-ink)}
.dk-filters button[aria-pressed="true"]{background:var(--dk-raise);color:var(--dk-ink);box-shadow:inset 0 0 0 1px var(--dk-edge)}
.dk button:focus-visible{outline:2px solid var(--dk-accent-text);outline-offset:2px}
.dk-form{position:relative;display:flex;gap:8px;margin-top:18px}
.dk-plus{position:absolute;left:13px;top:50%;translate:0 -50%;display:grid;color:var(--dk-muted);pointer-events:none}
.dk-form input{flex:1;min-width:0;font:inherit;font-size:1rem;line-height:1.4;padding:11px 14px 11px 40px;border:1px solid var(--dk-edge);border-radius:12px;background:#0f1218;color:var(--dk-ink);transition:border-color .15s}
.dk-form input::placeholder{color:#8f98ab}
.dk-form input:focus{outline:2px solid var(--dk-accent-text);outline-offset:1px;border-color:var(--dk-accent)}
.dk-add{flex:none;font:inherit;font-size:.95rem;font-weight:800;padding:0 18px;border:0;border-radius:12px;background:var(--dk-accent);color:var(--dk-accent-ink);cursor:pointer;transition:background-color .15s}
.dk-add:hover{background:var(--dk-accent-text)}
.dk-list{margin-top:14px!important;display:grid;gap:6px}
.dk-item{display:flex;align-items:center;gap:12px;padding:8px 6px 8px 14px;border:1px solid var(--dk-line);border-radius:12px;background:var(--dk-raise);transition:border-color .15s}
.dk-item:hover{border-color:var(--dk-edge)}
.dk-box{appearance:none;flex:none;width:22px;height:22px;margin:0;border:2px solid var(--dk-box);border-radius:50%;background:transparent center/13px no-repeat;cursor:pointer;transition:background-color .15s,border-color .15s}
.dk-box:checked{background-color:var(--dk-accent);border-color:var(--dk-accent);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%231a2e05' stroke-width='2.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8.5l3 3 6-7'/%3E%3C/svg%3E")}
.dk-box:focus-visible{outline:2px solid var(--dk-accent-text);outline-offset:3px}
.dk-item label{flex:1;min-width:0;font-size:1rem;line-height:1.4;padding:5px 0;cursor:pointer;overflow-wrap:anywhere;transition:color .15s}
.dk-item .dk-box:checked+label,.dk-litem .dk-box:checked+label{color:var(--dk-muted);text-decoration:line-through}
.dk-del{flex:none;display:grid;place-items:center;width:34px;height:34px;padding:0;border:0;border-radius:9px;background:transparent;color:var(--dk-muted);cursor:pointer;transition:background-color .15s,color .15s}
.dk-del:hover{background:var(--dk-danger-bg);color:var(--dk-danger)}
.dk-empty{padding:24px 8px!important;text-align:center;color:var(--dk-muted);font-size:.95rem}
.dk-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;font-size:.85rem;color:var(--dk-muted)}
.dk-clear{font:inherit;font-size:.85rem;font-weight:650;padding:6px 4px;border:0;background:none;color:var(--dk-accent-text);cursor:pointer}
.dk-clear:hover{text-decoration:underline}
.dk-clear:disabled{color:#7d8698;cursor:default;text-decoration:none}
/* top three */
.dk-tsub{margin-top:4px!important;font-size:.88rem;color:var(--dk-muted)}
.dk-top{margin-top:16px!important;display:grid;gap:10px}
.dk-titem{display:flex;align-items:center;gap:14px;padding:14px 16px;border:1px solid var(--dk-line);border-radius:14px;background:var(--dk-raise);transition:border-color .15s,background-color .15s}
.dk-titem:has(:checked){background:transparent;border-style:dashed}
.dk-num{flex:none;width:28px;font-size:1.6rem;font-weight:800;line-height:1;color:var(--dk-accent-text);font-variant-numeric:tabular-nums}
.dk-titem label{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px;cursor:pointer;order:2}
.dk-titem .dk-box{order:3;width:26px;height:26px;background-size:15px}
.dk-tname{font-size:1.02rem;font-weight:700;line-height:1.35}
.dk-tnote{font-size:.84rem;color:var(--dk-muted)}
.dk-box:checked+label .dk-tname{color:var(--dk-muted);text-decoration:line-through}
.dk-later{margin-top:22px}
.dk h3{font-size:.75rem;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:var(--dk-muted)}
.dk-llist{margin-top:6px!important}
.dk-litem{display:flex;align-items:center;gap:12px;padding:9px 0;border-top:1px solid var(--dk-line)}
.dk-litem .dk-box{width:18px;height:18px;background-size:11px}
.dk-litem label{flex:1;min-width:0;font-size:.95rem;line-height:1.4;cursor:pointer;color:#c9ceda}
.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){.dk{padding:20px 16px}.dk-titem{padding:12px}}
@media (prefers-reduced-motion:reduce){.dk *{transition:none!important}}
JavaScript
/* To-do app: add, tick off, delete, filter All / Active / Done, count what is left and remember the list
   in localStorage. Every [data-todo] root works on its own; new rows are cloned from its <template>. */
document.querySelectorAll('[data-todo]').forEach((root) => {
  const form = root.querySelector('[data-todo-form]');
  const field = form.querySelector('input');
  const list = root.querySelector('[data-todo-list]');
  const tpl = root.querySelector('[data-todo-tpl]');
  const count = root.querySelector('[data-todo-count]');
  const live = root.querySelector('[data-todo-live]');
  const empty = root.querySelector('[data-todo-empty]');
  const clear = root.querySelector('[data-todo-clear]');
  const filters = root.querySelectorAll('[data-filter]');
  const key = 'uic-todo-' + root.dataset.todo;
  let filter = 'all', seq = 0, tasks = null;

  try { tasks = JSON.parse(localStorage.getItem(key)); } catch (e) { /* storage blocked: start from the markup */ }
  if (!Array.isArray(tasks)) tasks = [...list.children].map((li) => ({
    text: li.querySelector('[data-text]').textContent.trim(), done: li.querySelector('input').checked }));
  tasks.forEach((t) => { t.id = ++seq; });

  const q = (s) => '“' + s + '”';
  const plural = (n, word) => n + ' ' + word + (n === 1 ? '' : 's');
  const left = () => plural(tasks.filter((t) => !t.done).length, 'task') + ' left';
  const shows = (t) => filter === 'all' || (filter === 'done') === t.done;
  const find = (li) => tasks.find((t) => t.id === +li.dataset.id);
  const save = () => { try { localStorage.setItem(key, JSON.stringify(tasks.map(({ text, done }) => ({ text, done })))); } catch (e) { /* ignore */ } };
  const say = (msg) => { live.textContent = ''; setTimeout(() => { live.textContent = msg; }, 60); };

  const row = (t) => {
    const li = tpl.content.firstElementChild.cloneNode(true);
    const box = li.querySelector('input');
    box.id = root.dataset.todo + '-task-' + t.id;
    box.checked = t.done;
    li.dataset.id = t.id;
    li.querySelector('label').htmlFor = box.id;
    li.querySelector('[data-text]').textContent = t.text;
    li.querySelector('[data-delete]').setAttribute('aria-label', 'Delete ' + q(t.text));
    return li;
  };
  // the visible neighbour that should take focus when a row leaves the view
  const near = (li) => {
    let n = li.nextElementSibling; while (n && n.hidden) n = n.nextElementSibling;
    let p = li.previousElementSibling; while (p && p.hidden) p = p.previousElementSibling;
    return (n || p) ? (n || p).querySelector('input') : field;
  };
  const sync = () => {
    [...list.children].forEach((li) => { li.hidden = !shows(find(li)); });
    if (count) count.textContent = left();
    if (clear) clear.disabled = !tasks.some((t) => t.done);
    if (empty) {
      empty.hidden = tasks.some(shows);
      empty.textContent = filter === 'active' ? 'Nothing left to do.' : filter === 'done' ? 'Nothing ticked off yet.' : 'No tasks yet. Add one above.';
    }
    filters.forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.filter === filter)));
  };

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const text = field.value.trim().replace(/\s+/g, ' ');
    if (!text) { say('Type a task first.'); field.focus(); return; }
    const t = { id: ++seq, text, done: false };
    tasks.push(t); list.append(row(t));
    if (filter === 'done') filter = 'all';
    field.value = ''; save(); sync();
    say('Task added: ' + q(text) + '. ' + left() + '.');
  });

  list.addEventListener('change', (e) => {
    const li = e.target.closest('li'), t = li && find(li);
    if (!t) return;
    t.done = e.target.checked;
    const next = shows(t) ? null : near(li);
    save(); sync();
    if (next) next.focus();
    say((t.done ? 'Done: ' : 'Not done: ') + q(t.text) + '. ' + left() + '.');
  });

  list.addEventListener('click', (e) => {
    const btn = e.target.closest('[data-delete]');
    if (!btn) return;
    const li = btn.closest('li'), t = find(li), next = near(li);
    tasks.splice(tasks.indexOf(t), 1); li.remove();
    save(); sync(); next.focus();
    say('Deleted ' + q(t.text) + '. ' + left() + '.');
  });

  filters.forEach((b) => b.addEventListener('click', () => {
    filter = b.dataset.filter; sync();
    const n = tasks.filter(shows).length;
    say(filter === 'all' ? 'Showing all ' + plural(n, 'task') : 'Showing ' + n + ' ' + filter + ' ' + (n === 1 ? 'task' : 'tasks'));
  }));

  if (clear) clear.addEventListener('click', () => {
    const gone = tasks.filter((t) => t.done).length;
    tasks = tasks.filter((t) => !t.done);
    [...list.children].forEach((li) => { if (!find(li)) li.remove(); });
    save(); sync(); field.focus();
    say('Cleared ' + plural(gone, 'finished task') + '. ' + left() + '.');
  });

  list.replaceChildren(...tasks.map(row));
  sync();
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Dark to-do app</span>
<section class="card dk" data-todo="studio" aria-labelledby="studio-title">
  <div class="dk-head"><div><h2 id="studio-title">Studio, this week</h2><p class="dk-count" data-todo-count>3 tasks left</p></div>
    <div class="dk-filters" role="group" aria-label="Show tasks"><button class="btn" type="button" data-filter="all" aria-pressed="true">All</button><button class="btn" type="button" data-filter="active" aria-pressed="false">Active</button><button class="btn" type="button" data-filter="done" aria-pressed="false">Done</button></div></div>
  <form class="dk-form" data-todo-form>
    <label class="visually-hidden" for="studio-new">New task</label>
    <span class="dk-plus"><svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M8 3v10M3 8h10"/></svg></span><input class="form-control" id="studio-new" type="text" placeholder="Add a task" autocomplete="off" maxlength="120">
    <button class="btn dk-add" type="submit">Add</button>
  </form>
  <ul class="dk-list" data-todo-list aria-label="Studio tasks">
    <li class="dk-item"><input class="form-check-input dk-box" type="checkbox" id="studio-task-1" checked><label for="studio-task-1" data-text>Export the logo set as SVG</label><button class="btn dk-del" type="button" data-delete aria-label="Delete “Export the logo set as SVG”"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li>
    <li class="dk-item"><input class="form-check-input dk-box" type="checkbox" id="studio-task-2"><label for="studio-task-2" data-text>Send the moodboard to Halden & Co</label><button class="btn dk-del" type="button" data-delete aria-label="Delete “Send the moodboard to Halden & Co”"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li>
    <li class="dk-item"><input class="form-check-input dk-box" type="checkbox" id="studio-task-3"><label for="studio-task-3" data-text>Fix the hover colour on the pricing cards</label><button class="btn dk-del" type="button" data-delete aria-label="Delete “Fix the hover colour on the pricing cards”"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li>
    <li class="dk-item"><input class="form-check-input dk-box" type="checkbox" id="studio-task-4"><label for="studio-task-4" data-text>Book the meeting room for Friday's review</label><button class="btn dk-del" type="button" data-delete aria-label="Delete “Book the meeting room for Friday's review”"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li>
  </ul>
  <p class="dk-empty" data-todo-empty hidden></p>
  <div class="dk-foot"><p>Saved in this browser</p><button class="btn dk-clear" type="button" data-todo-clear>Clear done</button></div>
  <p class="visually-hidden" role="status" data-todo-live></p>
  <template data-todo-tpl><li class="dk-item"><input class="form-check-input dk-box" type="checkbox"><label data-text></label><button class="btn dk-del" type="button" data-delete><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li></template>
</section></div>

<div><span class="lbl">Top three for today</span>
<section class="card dk" aria-labelledby="top-title">
  <h2 id="top-title">Today’s top three</h2>
  <p class="dk-tsub">Do these before anything else. Everything else can wait.</p>
  <ol class="dk-top">
    <li class="dk-titem"><span class="dk-num" aria-hidden="true">1</span><input class="form-check-input dk-box" type="checkbox" id="top-1" checked><label for="top-1"><span class="dk-tname">Finish the onboarding flow mockups</span><span class="dk-tnote">Due before the 15:00 review</span></label></li>
    <li class="dk-titem"><span class="dk-num" aria-hidden="true">2</span><input class="form-check-input dk-box" type="checkbox" id="top-2"><label for="top-2"><span class="dk-tname">Reply to Dana about the API limits</span><span class="dk-tnote">She is waiting on a number</span></label></li>
    <li class="dk-titem"><span class="dk-num" aria-hidden="true">3</span><input class="form-check-input dk-box" type="checkbox" id="top-3"><label for="top-3"><span class="dk-tname">Record the five-minute demo video</span><span class="dk-tnote">Use the staging account</span></label></li>
  </ol>
  <section class="dk-later" aria-labelledby="later-title">
    <h3 id="later-title">Later</h3>
    <ul class="dk-llist">
      <li class="dk-litem"><input class="form-check-input dk-box" type="checkbox" id="later-1"><label for="later-1">Order a new monitor arm</label></li>
      <li class="dk-litem"><input class="form-check-input dk-box" type="checkbox" id="later-2"><label for="later-2">Read the accessibility audit</label></li>
      <li class="dk-litem"><input class="form-check-input dk-box" type="checkbox" id="later-3" checked><label for="later-3">Clear out the shared Downloads folder</label></li>
    </ul>
  </section>
</section></div>
CSS
/* Dark to-do lists on Bootstrap, in its dark colour mode (data-bs-theme="dark"): .card, .form-control, .form-check-input and .btn restyled through CSS variables. The app is the same script as the classic set (add, tick off, delete, filter, count,
   localStorage); colours are tuned for a near-black ground and checked for contrast. The top-three list needs no script. */
.dk{--bs-card-bg:var(--dk-bg);--bs-card-border-color:var(--dk-line);--bs-card-border-radius:20px;--dk-bg:#141821;--dk-raise:#1b202b;--dk-line:#262d3b;--dk-edge:#3a4356;--dk-ink:#e8eaf0;--dk-muted:#9aa3b5;--dk-box:#7a849a;
  --dk-accent:#a3e635;--dk-accent-ink:#1a2e05;--dk-accent-text:#bef264;--dk-danger:#fca5a5;--dk-danger-bg:#3a1d22;
  background:var(--dk-bg);color:var(--dk-ink);border:1px solid var(--dk-line);border-radius:20px;padding:24px;box-shadow:0 30px 60px -30px #000}
.dk [hidden]{display:none!important}
.dk :where(h2,h3,p,ul,ol){margin:0;padding:0}
.dk label{margin:0}
.dk :where(h2,h3){line-height:normal;color:var(--dk-ink)}
.dk :where(ul,ol){list-style:none}
.dk h2{font-size:1.25rem;font-weight:800;letter-spacing:-.015em}
.dk-head{display:flex;align-items:center;justify-content:space-between;gap:12px 16px;flex-wrap:wrap}
.dk-count{margin-top:3px!important;font-size:.85rem;color:var(--dk-muted)}
.dk-filters{display:flex;gap:2px;padding:3px;border:1px solid var(--dk-line);border-radius:10px;background:#0f1218}
.dk-filters .btn{--bs-btn-padding-x:12px;--bs-btn-padding-y:6px;--bs-btn-font-size:.82rem;--bs-btn-font-weight:650;--bs-btn-line-height:normal;--bs-btn-border-width:0;--bs-btn-border-radius:7px;--bs-btn-color:var(--dk-muted);--bs-btn-hover-color:var(--dk-ink);--bs-btn-active-color:var(--dk-ink)}
.dk-filters button[aria-pressed="true"]{background:var(--dk-raise);color:var(--dk-ink);box-shadow:inset 0 0 0 1px var(--dk-edge)}
.dk .btn:focus-visible,.dk button:focus-visible{box-shadow:none;outline:2px solid var(--dk-accent-text);outline-offset:2px}
.dk-form{position:relative;display:flex;gap:8px;margin-top:18px}
.dk-plus{position:absolute;left:13px;top:50%;translate:0 -50%;display:grid;color:var(--dk-muted);pointer-events:none}
.dk-form .form-control{flex:1;box-shadow:none;min-width:0;font:inherit;font-size:1rem;line-height:1.4;padding:11px 14px 11px 40px;border:1px solid var(--dk-edge);border-radius:12px;background:#0f1218;color:var(--dk-ink);transition:border-color .15s}
.dk-form .form-control::placeholder{color:#8f98ab}
.dk-form .form-control:focus{box-shadow:none;outline:2px solid var(--dk-accent-text);outline-offset:1px;border-color:var(--dk-accent);background:#0f1218;color:var(--dk-ink)}
.dk-add{--bs-btn-padding-x:18px;--bs-btn-font-size:.95rem;--bs-btn-font-weight:800;--bs-btn-border-width:0;--bs-btn-border-radius:12px;--bs-btn-bg:var(--dk-accent);--bs-btn-color:var(--dk-accent-ink);--bs-btn-hover-bg:var(--dk-accent-text);--bs-btn-hover-color:var(--dk-accent-ink);--bs-btn-active-bg:var(--dk-accent-text);--bs-btn-active-color:var(--dk-accent-ink);flex:none;display:inline-flex;align-items:center}
.dk-list{margin-top:14px!important;display:grid;gap:6px}
.dk-item{display:flex;align-items:center;gap:12px;padding:8px 6px 8px 14px;border:1px solid var(--dk-line);border-radius:12px;background:var(--dk-raise);transition:border-color .15s}
.dk-item:hover{border-color:var(--dk-edge)}
.dk .dk-box{flex:none;width:22px;height:22px;margin:0;border:2px solid var(--dk-box);border-radius:50%;background-color:transparent;background-size:13px;cursor:pointer;transition:background-color .15s,border-color .15s}
.dk .dk-box:checked{background-color:var(--dk-accent);border-color:var(--dk-accent);--bs-form-check-bg-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%231a2e05' stroke-width='2.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8.5l3 3 6-7'/%3E%3C/svg%3E")}
.dk .dk-box:focus{box-shadow:none}
.dk .dk-box:focus-visible{outline:2px solid var(--dk-accent-text);outline-offset:3px}
.dk-item label{flex:1;min-width:0;font-size:1rem;line-height:1.4;padding:5px 0;cursor:pointer;overflow-wrap:anywhere;transition:color .15s}
.dk-item .dk-box:checked+label,.dk-litem .dk-box:checked+label{color:var(--dk-muted);text-decoration:line-through}
.dk-del{--bs-btn-padding-x:0;--bs-btn-padding-y:0;--bs-btn-border-width:0;--bs-btn-border-radius:9px;--bs-btn-color:var(--dk-muted);--bs-btn-hover-color:var(--dk-danger);--bs-btn-hover-bg:var(--dk-danger-bg);--bs-btn-active-color:var(--dk-danger);--bs-btn-active-bg:var(--dk-danger-bg);flex:none;display:grid;place-items:center;width:34px;height:34px}
.dk-empty{padding:24px 8px!important;text-align:center;color:var(--dk-muted);font-size:.95rem}
.dk-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;font-size:.85rem;color:var(--dk-muted)}
.dk-clear{--bs-btn-padding-x:4px;--bs-btn-padding-y:6px;--bs-btn-font-size:.85rem;--bs-btn-font-weight:650;--bs-btn-line-height:normal;--bs-btn-border-width:0;--bs-btn-color:var(--dk-accent-text);--bs-btn-hover-color:var(--dk-accent-text);--bs-btn-active-color:var(--dk-accent-text);--bs-btn-disabled-color:#7d8698;--bs-btn-disabled-opacity:1}
.dk-clear:hover{text-decoration:underline}
/* top three */
.dk-tsub{margin-top:4px!important;font-size:.88rem;color:var(--dk-muted)}
.dk-top{margin-top:16px!important;display:grid;gap:10px}
.dk-titem{display:flex;align-items:center;gap:14px;padding:14px 16px;border:1px solid var(--dk-line);border-radius:14px;background:var(--dk-raise);transition:border-color .15s,background-color .15s}
.dk-titem:has(:checked){background:transparent;border-style:dashed}
.dk-num{flex:none;width:28px;font-size:1.6rem;font-weight:800;line-height:1;color:var(--dk-accent-text);font-variant-numeric:tabular-nums}
.dk-titem label{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px;cursor:pointer;order:2}
.dk .dk-titem .dk-box{order:3;width:26px;height:26px;background-size:15px}
.dk-tname{font-size:1.02rem;font-weight:700;line-height:1.35}
.dk-tnote{font-size:.84rem;color:var(--dk-muted)}
.dk-box:checked+label .dk-tname{color:var(--dk-muted);text-decoration:line-through}
.dk-later{margin-top:22px}
.dk h3{font-size:.75rem;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:var(--dk-muted)}
.dk-llist{margin-top:6px!important}
.dk-litem{display:flex;align-items:center;gap:12px;padding:9px 0;border-top:1px solid var(--dk-line)}
.dk .dk-litem .dk-box{width:18px;height:18px;background-size:11px}
.dk-litem label{flex:1;min-width:0;font-size:.95rem;line-height:1.4;cursor:pointer;color:#c9ceda}
@media (max-width:480px){.dk{padding:20px 16px}.dk-titem{padding:12px}}
@media (prefers-reduced-motion:reduce){.dk *{transition:none!important}}
JavaScript
/* To-do app: add, tick off, delete, filter All / Active / Done, count what is left and remember the list
   in localStorage. Every [data-todo] root works on its own; new rows are cloned from its <template>. */
document.querySelectorAll('[data-todo]').forEach((root) => {
  const form = root.querySelector('[data-todo-form]');
  const field = form.querySelector('input');
  const list = root.querySelector('[data-todo-list]');
  const tpl = root.querySelector('[data-todo-tpl]');
  const count = root.querySelector('[data-todo-count]');
  const live = root.querySelector('[data-todo-live]');
  const empty = root.querySelector('[data-todo-empty]');
  const clear = root.querySelector('[data-todo-clear]');
  const filters = root.querySelectorAll('[data-filter]');
  const key = 'uic-todo-' + root.dataset.todo;
  let filter = 'all', seq = 0, tasks = null;

  try { tasks = JSON.parse(localStorage.getItem(key)); } catch (e) { /* storage blocked: start from the markup */ }
  if (!Array.isArray(tasks)) tasks = [...list.children].map((li) => ({
    text: li.querySelector('[data-text]').textContent.trim(), done: li.querySelector('input').checked }));
  tasks.forEach((t) => { t.id = ++seq; });

  const q = (s) => '“' + s + '”';
  const plural = (n, word) => n + ' ' + word + (n === 1 ? '' : 's');
  const left = () => plural(tasks.filter((t) => !t.done).length, 'task') + ' left';
  const shows = (t) => filter === 'all' || (filter === 'done') === t.done;
  const find = (li) => tasks.find((t) => t.id === +li.dataset.id);
  const save = () => { try { localStorage.setItem(key, JSON.stringify(tasks.map(({ text, done }) => ({ text, done })))); } catch (e) { /* ignore */ } };
  const say = (msg) => { live.textContent = ''; setTimeout(() => { live.textContent = msg; }, 60); };

  const row = (t) => {
    const li = tpl.content.firstElementChild.cloneNode(true);
    const box = li.querySelector('input');
    box.id = root.dataset.todo + '-task-' + t.id;
    box.checked = t.done;
    li.dataset.id = t.id;
    li.querySelector('label').htmlFor = box.id;
    li.querySelector('[data-text]').textContent = t.text;
    li.querySelector('[data-delete]').setAttribute('aria-label', 'Delete ' + q(t.text));
    return li;
  };
  // the visible neighbour that should take focus when a row leaves the view
  const near = (li) => {
    let n = li.nextElementSibling; while (n && n.hidden) n = n.nextElementSibling;
    let p = li.previousElementSibling; while (p && p.hidden) p = p.previousElementSibling;
    return (n || p) ? (n || p).querySelector('input') : field;
  };
  const sync = () => {
    [...list.children].forEach((li) => { li.hidden = !shows(find(li)); });
    if (count) count.textContent = left();
    if (clear) clear.disabled = !tasks.some((t) => t.done);
    if (empty) {
      empty.hidden = tasks.some(shows);
      empty.textContent = filter === 'active' ? 'Nothing left to do.' : filter === 'done' ? 'Nothing ticked off yet.' : 'No tasks yet. Add one above.';
    }
    filters.forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.filter === filter)));
  };

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const text = field.value.trim().replace(/\s+/g, ' ');
    if (!text) { say('Type a task first.'); field.focus(); return; }
    const t = { id: ++seq, text, done: false };
    tasks.push(t); list.append(row(t));
    if (filter === 'done') filter = 'all';
    field.value = ''; save(); sync();
    say('Task added: ' + q(text) + '. ' + left() + '.');
  });

  list.addEventListener('change', (e) => {
    const li = e.target.closest('li'), t = li && find(li);
    if (!t) return;
    t.done = e.target.checked;
    const next = shows(t) ? null : near(li);
    save(); sync();
    if (next) next.focus();
    say((t.done ? 'Done: ' : 'Not done: ') + q(t.text) + '. ' + left() + '.');
  });

  list.addEventListener('click', (e) => {
    const btn = e.target.closest('[data-delete]');
    if (!btn) return;
    const li = btn.closest('li'), t = find(li), next = near(li);
    tasks.splice(tasks.indexOf(t), 1); li.remove();
    save(); sync(); next.focus();
    say('Deleted ' + q(t.text) + '. ' + left() + '.');
  });

  filters.forEach((b) => b.addEventListener('click', () => {
    filter = b.dataset.filter; sync();
    const n = tasks.filter(shows).length;
    say(filter === 'all' ? 'Showing all ' + plural(n, 'task') : 'Showing ' + n + ' ' + filter + ' ' + (n === 1 ? 'task' : 'tasks'));
  }));

  if (clear) clear.addEventListener('click', () => {
    const gone = tasks.filter((t) => t.done).length;
    tasks = tasks.filter((t) => !t.done);
    [...list.children].forEach((li) => { if (!find(li)) li.remove(); });
    save(); sync(); field.focus();
    say('Cleared ' + plural(gone, 'finished task') + '. ' + left() + '.');
  });

  list.replaceChildren(...tasks.map(row));
  sync();
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#9aa3b5] uppercase">Dark to-do app</span>
<section class="rounded-[20px] border border-[#262d3b] bg-[#141821] p-6 text-[#e8eaf0] shadow-[0_30px_60px_-30px_#000] max-[480px]:px-4 max-[480px]:py-5" data-todo="studio" aria-labelledby="studio-title">
  <div class="flex flex-wrap items-center justify-between gap-x-4 gap-y-3"><div><h2 class="text-[1.25rem] font-extrabold tracking-[-.015em]" id="studio-title">Studio, this week</h2><p class="mt-[3px] text-[.85rem] text-[#9aa3b5]" data-todo-count>3 tasks left</p></div>
    <div class="flex gap-0.5 rounded-[10px] border border-[#262d3b] bg-[#0f1218] p-[3px]" role="group" aria-label="Show tasks"><button class="cursor-pointer rounded-[7px] px-3 py-1.5 text-[.82rem] font-[650] text-[#9aa3b5] hover:text-[#e8eaf0] aria-pressed:bg-[#1b202b] aria-pressed:text-[#e8eaf0] aria-pressed:shadow-[inset_0_0_0_1px_#3a4356] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#bef264]" type="button" data-filter="all" aria-pressed="true">All</button><button class="cursor-pointer rounded-[7px] px-3 py-1.5 text-[.82rem] font-[650] text-[#9aa3b5] hover:text-[#e8eaf0] aria-pressed:bg-[#1b202b] aria-pressed:text-[#e8eaf0] aria-pressed:shadow-[inset_0_0_0_1px_#3a4356] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#bef264]" type="button" data-filter="active" aria-pressed="false">Active</button><button class="cursor-pointer rounded-[7px] px-3 py-1.5 text-[.82rem] font-[650] text-[#9aa3b5] hover:text-[#e8eaf0] aria-pressed:bg-[#1b202b] aria-pressed:text-[#e8eaf0] aria-pressed:shadow-[inset_0_0_0_1px_#3a4356] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#bef264]" type="button" data-filter="done" aria-pressed="false">Done</button></div></div>
  <form class="relative mt-[18px] flex gap-2" data-todo-form>
    <label class="sr-only" for="studio-new">New task</label>
    <span class="pointer-events-none absolute top-1/2 left-[13px] grid -translate-y-1/2 text-[#9aa3b5]"><svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M8 3v10M3 8h10"/></svg></span><input class="min-w-0 flex-1 rounded-xl border border-[#3a4356] bg-[#0f1218] py-[11px] pr-3.5 pl-10 text-base leading-[1.4] text-[#e8eaf0] placeholder:text-[#8f98ab] transition-colors duration-150 focus:border-[#a3e635] focus:outline-2 focus:outline-offset-1 focus:outline-[#bef264] motion-reduce:transition-none" id="studio-new" type="text" placeholder="Add a task" autocomplete="off" maxlength="120">
    <button class="flex-none cursor-pointer rounded-xl bg-[#a3e635] px-[18px] text-[.95rem] font-extrabold text-[#1a2e05] transition-colors duration-150 hover:bg-[#bef264] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="submit">Add</button>
  </form>
  <ul class="mt-3.5 grid gap-1.5" data-todo-list aria-label="Studio tasks">
    <li class="flex items-center gap-3 rounded-xl border border-[#262d3b] bg-[#1b202b] py-2 pr-1.5 pl-3.5 transition-colors duration-150 hover:border-[#3a4356] motion-reduce:transition-none"><input class="peer size-[22px] bg-[length:13px] flex-none cursor-pointer appearance-none rounded-full border-2 border-[#7a849a] bg-transparent bg-center bg-no-repeat transition-colors duration-150 checked:border-[#a3e635] checked:bg-[#a3e635] checked:bg-tick focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="checkbox" id="studio-task-1" checked><label class="min-w-0 flex-1 cursor-pointer py-[5px] text-base leading-[1.4] [overflow-wrap:anywhere] transition-colors duration-150 peer-checked:text-[#9aa3b5] peer-checked:line-through motion-reduce:transition-none" for="studio-task-1" data-text>Export the logo set as SVG</label><button class="grid size-[34px] flex-none cursor-pointer place-items-center rounded-[9px] text-[#9aa3b5] transition-colors duration-150 hover:bg-[#3a1d22] hover:text-[#fca5a5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="button" data-delete aria-label="Delete “Export the logo set as SVG”"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li>
    <li class="flex items-center gap-3 rounded-xl border border-[#262d3b] bg-[#1b202b] py-2 pr-1.5 pl-3.5 transition-colors duration-150 hover:border-[#3a4356] motion-reduce:transition-none"><input class="peer size-[22px] bg-[length:13px] flex-none cursor-pointer appearance-none rounded-full border-2 border-[#7a849a] bg-transparent bg-center bg-no-repeat transition-colors duration-150 checked:border-[#a3e635] checked:bg-[#a3e635] checked:bg-tick focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="checkbox" id="studio-task-2"><label class="min-w-0 flex-1 cursor-pointer py-[5px] text-base leading-[1.4] [overflow-wrap:anywhere] transition-colors duration-150 peer-checked:text-[#9aa3b5] peer-checked:line-through motion-reduce:transition-none" for="studio-task-2" data-text>Send the moodboard to Halden & Co</label><button class="grid size-[34px] flex-none cursor-pointer place-items-center rounded-[9px] text-[#9aa3b5] transition-colors duration-150 hover:bg-[#3a1d22] hover:text-[#fca5a5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="button" data-delete aria-label="Delete “Send the moodboard to Halden & Co”"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li>
    <li class="flex items-center gap-3 rounded-xl border border-[#262d3b] bg-[#1b202b] py-2 pr-1.5 pl-3.5 transition-colors duration-150 hover:border-[#3a4356] motion-reduce:transition-none"><input class="peer size-[22px] bg-[length:13px] flex-none cursor-pointer appearance-none rounded-full border-2 border-[#7a849a] bg-transparent bg-center bg-no-repeat transition-colors duration-150 checked:border-[#a3e635] checked:bg-[#a3e635] checked:bg-tick focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="checkbox" id="studio-task-3"><label class="min-w-0 flex-1 cursor-pointer py-[5px] text-base leading-[1.4] [overflow-wrap:anywhere] transition-colors duration-150 peer-checked:text-[#9aa3b5] peer-checked:line-through motion-reduce:transition-none" for="studio-task-3" data-text>Fix the hover colour on the pricing cards</label><button class="grid size-[34px] flex-none cursor-pointer place-items-center rounded-[9px] text-[#9aa3b5] transition-colors duration-150 hover:bg-[#3a1d22] hover:text-[#fca5a5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="button" data-delete aria-label="Delete “Fix the hover colour on the pricing cards”"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li>
    <li class="flex items-center gap-3 rounded-xl border border-[#262d3b] bg-[#1b202b] py-2 pr-1.5 pl-3.5 transition-colors duration-150 hover:border-[#3a4356] motion-reduce:transition-none"><input class="peer size-[22px] bg-[length:13px] flex-none cursor-pointer appearance-none rounded-full border-2 border-[#7a849a] bg-transparent bg-center bg-no-repeat transition-colors duration-150 checked:border-[#a3e635] checked:bg-[#a3e635] checked:bg-tick focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="checkbox" id="studio-task-4"><label class="min-w-0 flex-1 cursor-pointer py-[5px] text-base leading-[1.4] [overflow-wrap:anywhere] transition-colors duration-150 peer-checked:text-[#9aa3b5] peer-checked:line-through motion-reduce:transition-none" for="studio-task-4" data-text>Book the meeting room for Friday's review</label><button class="grid size-[34px] flex-none cursor-pointer place-items-center rounded-[9px] text-[#9aa3b5] transition-colors duration-150 hover:bg-[#3a1d22] hover:text-[#fca5a5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="button" data-delete aria-label="Delete “Book the meeting room for Friday's review”"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li>
  </ul>
  <p class="px-2 py-6 text-center text-[.95rem] text-[#9aa3b5]" data-todo-empty hidden></p>
  <div class="mt-3.5 flex items-center justify-between gap-2.5 text-[.85rem] text-[#9aa3b5]"><p>Saved in this browser</p><button class="cursor-pointer px-1 py-1.5 text-[.85rem] font-[650] text-[#bef264] hover:underline disabled:cursor-default disabled:text-[#7d8698] disabled:no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#bef264]" type="button" data-todo-clear>Clear done</button></div>
  <p class="sr-only" role="status" data-todo-live></p>
  <template data-todo-tpl><li class="flex items-center gap-3 rounded-xl border border-[#262d3b] bg-[#1b202b] py-2 pr-1.5 pl-3.5 transition-colors duration-150 hover:border-[#3a4356] motion-reduce:transition-none"><input class="peer size-[22px] bg-[length:13px] flex-none cursor-pointer appearance-none rounded-full border-2 border-[#7a849a] bg-transparent bg-center bg-no-repeat transition-colors duration-150 checked:border-[#a3e635] checked:bg-[#a3e635] checked:bg-tick focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="checkbox"><label class="min-w-0 flex-1 cursor-pointer py-[5px] text-base leading-[1.4] [overflow-wrap:anywhere] transition-colors duration-150 peer-checked:text-[#9aa3b5] peer-checked:line-through motion-reduce:transition-none" data-text></label><button class="grid size-[34px] flex-none cursor-pointer place-items-center rounded-[9px] text-[#9aa3b5] transition-colors duration-150 hover:bg-[#3a1d22] hover:text-[#fca5a5] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="button" data-delete><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8"/></svg></button></li></template>
</section></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#9aa3b5] uppercase">Top three for today</span>
<section class="rounded-[20px] border border-[#262d3b] bg-[#141821] p-6 text-[#e8eaf0] shadow-[0_30px_60px_-30px_#000] max-[480px]:px-4 max-[480px]:py-5" aria-labelledby="top-title">
  <h2 class="text-[1.25rem] font-extrabold tracking-[-.015em]" id="top-title">Today’s top three</h2>
  <p class="mt-1 text-[.88rem] text-[#9aa3b5]">Do these before anything else. Everything else can wait.</p>
  <ol class="mt-4 grid gap-2.5">
    <li class="flex items-center gap-3.5 rounded-[14px] border border-[#262d3b] bg-[#1b202b] px-4 py-3.5 transition-colors duration-150 has-checked:border-dashed has-checked:bg-transparent motion-reduce:transition-none max-[480px]:p-3"><span class="w-7 flex-none text-[1.6rem] leading-none font-extrabold text-[#bef264] tabular-nums" aria-hidden="true">1</span><input class="peer order-3 size-[26px] bg-[length:15px] flex-none cursor-pointer appearance-none rounded-full border-2 border-[#7a849a] bg-transparent bg-center bg-no-repeat transition-colors duration-150 checked:border-[#a3e635] checked:bg-[#a3e635] checked:bg-tick focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="checkbox" id="top-1" checked><label class="order-2 flex peer-checked:[&>span:first-child]:text-[#9aa3b5] peer-checked:[&>span:first-child]:line-through min-w-0 flex-1 cursor-pointer flex-col gap-[3px]" for="top-1"><span class="text-[1.02rem] leading-[1.35] font-bold">Finish the onboarding flow mockups</span><span class="text-[.84rem] text-[#9aa3b5]">Due before the 15:00 review</span></label></li>
    <li class="flex items-center gap-3.5 rounded-[14px] border border-[#262d3b] bg-[#1b202b] px-4 py-3.5 transition-colors duration-150 has-checked:border-dashed has-checked:bg-transparent motion-reduce:transition-none max-[480px]:p-3"><span class="w-7 flex-none text-[1.6rem] leading-none font-extrabold text-[#bef264] tabular-nums" aria-hidden="true">2</span><input class="peer order-3 size-[26px] bg-[length:15px] flex-none cursor-pointer appearance-none rounded-full border-2 border-[#7a849a] bg-transparent bg-center bg-no-repeat transition-colors duration-150 checked:border-[#a3e635] checked:bg-[#a3e635] checked:bg-tick focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="checkbox" id="top-2"><label class="order-2 flex peer-checked:[&>span:first-child]:text-[#9aa3b5] peer-checked:[&>span:first-child]:line-through min-w-0 flex-1 cursor-pointer flex-col gap-[3px]" for="top-2"><span class="text-[1.02rem] leading-[1.35] font-bold">Reply to Dana about the API limits</span><span class="text-[.84rem] text-[#9aa3b5]">She is waiting on a number</span></label></li>
    <li class="flex items-center gap-3.5 rounded-[14px] border border-[#262d3b] bg-[#1b202b] px-4 py-3.5 transition-colors duration-150 has-checked:border-dashed has-checked:bg-transparent motion-reduce:transition-none max-[480px]:p-3"><span class="w-7 flex-none text-[1.6rem] leading-none font-extrabold text-[#bef264] tabular-nums" aria-hidden="true">3</span><input class="peer order-3 size-[26px] bg-[length:15px] flex-none cursor-pointer appearance-none rounded-full border-2 border-[#7a849a] bg-transparent bg-center bg-no-repeat transition-colors duration-150 checked:border-[#a3e635] checked:bg-[#a3e635] checked:bg-tick focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="checkbox" id="top-3"><label class="order-2 flex peer-checked:[&>span:first-child]:text-[#9aa3b5] peer-checked:[&>span:first-child]:line-through min-w-0 flex-1 cursor-pointer flex-col gap-[3px]" for="top-3"><span class="text-[1.02rem] leading-[1.35] font-bold">Record the five-minute demo video</span><span class="text-[.84rem] text-[#9aa3b5]">Use the staging account</span></label></li>
  </ol>
  <section class="mt-[22px]" aria-labelledby="later-title">
    <h3 class="text-[.75rem] font-extrabold tracking-[.12em] text-[#9aa3b5] uppercase" id="later-title">Later</h3>
    <ul class="mt-1.5">
      <li class="flex items-center gap-3 border-t border-[#262d3b] py-[9px]"><input class="peer size-[18px] bg-[length:11px] flex-none cursor-pointer appearance-none rounded-full border-2 border-[#7a849a] bg-transparent bg-center bg-no-repeat transition-colors duration-150 checked:border-[#a3e635] checked:bg-[#a3e635] checked:bg-tick focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="checkbox" id="later-1"><label class="min-w-0 flex-1 cursor-pointer text-[.95rem] leading-[1.4] text-[#c9ceda] peer-checked:text-[#9aa3b5] peer-checked:line-through" for="later-1">Order a new monitor arm</label></li>
      <li class="flex items-center gap-3 border-t border-[#262d3b] py-[9px]"><input class="peer size-[18px] bg-[length:11px] flex-none cursor-pointer appearance-none rounded-full border-2 border-[#7a849a] bg-transparent bg-center bg-no-repeat transition-colors duration-150 checked:border-[#a3e635] checked:bg-[#a3e635] checked:bg-tick focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="checkbox" id="later-2"><label class="min-w-0 flex-1 cursor-pointer text-[.95rem] leading-[1.4] text-[#c9ceda] peer-checked:text-[#9aa3b5] peer-checked:line-through" for="later-2">Read the accessibility audit</label></li>
      <li class="flex items-center gap-3 border-t border-[#262d3b] py-[9px]"><input class="peer size-[18px] bg-[length:11px] flex-none cursor-pointer appearance-none rounded-full border-2 border-[#7a849a] bg-transparent bg-center bg-no-repeat transition-colors duration-150 checked:border-[#a3e635] checked:bg-[#a3e635] checked:bg-tick focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#bef264] motion-reduce:transition-none" type="checkbox" id="later-3" checked><label class="min-w-0 flex-1 cursor-pointer text-[.95rem] leading-[1.4] text-[#c9ceda] peer-checked:text-[#9aa3b5] peer-checked:line-through" for="later-3">Clear out the shared Downloads folder</label></li>
    </ul>
  </section>
</section></div>
CSS
/* the dark tick drawn inside a checked lime circle */
@utility bg-tick {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%231a2e05' stroke-width='2.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8.5l3 3 6-7'/%3E%3C/svg%3E");
}
JavaScript
/* To-do app: add, tick off, delete, filter All / Active / Done, count what is left and remember the list
   in localStorage. Every [data-todo] root works on its own; new rows are cloned from its <template>. */
document.querySelectorAll('[data-todo]').forEach((root) => {
  const form = root.querySelector('[data-todo-form]');
  const field = form.querySelector('input');
  const list = root.querySelector('[data-todo-list]');
  const tpl = root.querySelector('[data-todo-tpl]');
  const count = root.querySelector('[data-todo-count]');
  const live = root.querySelector('[data-todo-live]');
  const empty = root.querySelector('[data-todo-empty]');
  const clear = root.querySelector('[data-todo-clear]');
  const filters = root.querySelectorAll('[data-filter]');
  const key = 'uic-todo-' + root.dataset.todo;
  let filter = 'all', seq = 0, tasks = null;

  try { tasks = JSON.parse(localStorage.getItem(key)); } catch (e) { /* storage blocked: start from the markup */ }
  if (!Array.isArray(tasks)) tasks = [...list.children].map((li) => ({
    text: li.querySelector('[data-text]').textContent.trim(), done: li.querySelector('input').checked }));
  tasks.forEach((t) => { t.id = ++seq; });

  const q = (s) => '“' + s + '”';
  const plural = (n, word) => n + ' ' + word + (n === 1 ? '' : 's');
  const left = () => plural(tasks.filter((t) => !t.done).length, 'task') + ' left';
  const shows = (t) => filter === 'all' || (filter === 'done') === t.done;
  const find = (li) => tasks.find((t) => t.id === +li.dataset.id);
  const save = () => { try { localStorage.setItem(key, JSON.stringify(tasks.map(({ text, done }) => ({ text, done })))); } catch (e) { /* ignore */ } };
  const say = (msg) => { live.textContent = ''; setTimeout(() => { live.textContent = msg; }, 60); };

  const row = (t) => {
    const li = tpl.content.firstElementChild.cloneNode(true);
    const box = li.querySelector('input');
    box.id = root.dataset.todo + '-task-' + t.id;
    box.checked = t.done;
    li.dataset.id = t.id;
    li.querySelector('label').htmlFor = box.id;
    li.querySelector('[data-text]').textContent = t.text;
    li.querySelector('[data-delete]').setAttribute('aria-label', 'Delete ' + q(t.text));
    return li;
  };
  // the visible neighbour that should take focus when a row leaves the view
  const near = (li) => {
    let n = li.nextElementSibling; while (n && n.hidden) n = n.nextElementSibling;
    let p = li.previousElementSibling; while (p && p.hidden) p = p.previousElementSibling;
    return (n || p) ? (n || p).querySelector('input') : field;
  };
  const sync = () => {
    [...list.children].forEach((li) => { li.hidden = !shows(find(li)); });
    if (count) count.textContent = left();
    if (clear) clear.disabled = !tasks.some((t) => t.done);
    if (empty) {
      empty.hidden = tasks.some(shows);
      empty.textContent = filter === 'active' ? 'Nothing left to do.' : filter === 'done' ? 'Nothing ticked off yet.' : 'No tasks yet. Add one above.';
    }
    filters.forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.filter === filter)));
  };

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const text = field.value.trim().replace(/\s+/g, ' ');
    if (!text) { say('Type a task first.'); field.focus(); return; }
    const t = { id: ++seq, text, done: false };
    tasks.push(t); list.append(row(t));
    if (filter === 'done') filter = 'all';
    field.value = ''; save(); sync();
    say('Task added: ' + q(text) + '. ' + left() + '.');
  });

  list.addEventListener('change', (e) => {
    const li = e.target.closest('li'), t = li && find(li);
    if (!t) return;
    t.done = e.target.checked;
    const next = shows(t) ? null : near(li);
    save(); sync();
    if (next) next.focus();
    say((t.done ? 'Done: ' : 'Not done: ') + q(t.text) + '. ' + left() + '.');
  });

  list.addEventListener('click', (e) => {
    const btn = e.target.closest('[data-delete]');
    if (!btn) return;
    const li = btn.closest('li'), t = find(li), next = near(li);
    tasks.splice(tasks.indexOf(t), 1); li.remove();
    save(); sync(); next.focus();
    say('Deleted ' + q(t.text) + '. ' + left() + '.');
  });

  filters.forEach((b) => b.addEventListener('click', () => {
    filter = b.dataset.filter; sync();
    const n = tasks.filter(shows).length;
    say(filter === 'all' ? 'Showing all ' + plural(n, 'task') : 'Showing ' + n + ' ' + filter + ' ' + (n === 1 ? 'task' : 'tasks'));
  }));

  if (clear) clear.addEventListener('click', () => {
    const gone = tasks.filter((t) => t.done).length;
    tasks = tasks.filter((t) => !t.done);
    [...list.children].forEach((li) => { if (!find(li)) li.remove(); });
    save(); sync(); field.focus();
    say('Cleared ' + plural(gone, 'finished task') + '. ' + left() + '.');
  });

  list.replaceChildren(...tasks.map(row));
  sync();
});

About this set

To-do lists for dark interfaces, with colours checked for contrast on a near-black ground. Dark to-do app is a design studio’s week: the title, a count of tasks left and the All, Active and Done filters share the header, the add field has a plus icon inside it next to a lime Add button, and each task is a raised tile with a round lime checkbox and a delete button. It runs the same vanilla script as the classic set, so it adds, ticks off, deletes, filters, clears finished tasks and saves the list to localStorage under its own key, announcing each change. Top three for today is a focus list for the three tasks that matter most: large lime numbers, a note under each task and a big round checkbox on the right, with finished tiles turning to a dashed outline through :has(). A short Later list sits underneath with smaller boxes. The top three list needs no script. The Bootstrap version runs in Bootstrap’s dark colour mode with data-bs-theme=”dark” and restyles .card, .form-control, .form-check-input and .btn.

What’s included

  • Dark to-do app with add, tick, delete, filters, count and localStorage
  • Lime accent with dark text on it, checked for contrast
  • Top three focus list with numbered tiles and notes, no JavaScript
  • Finished tiles switch to a dashed outline through :has()
  • Bootstrap version in data-bs-theme="dark"

Accessibility

Every task is a real checkbox with a label, delete buttons are named after their task, and the filters use aria-pressed. The app announces added, ticked and deleted tasks with the number left in a polite status region and moves focus to the next row after a delete. Muted text, checkbox outlines and the lime accent meet WCAG AA contrast on the dark ground, focus outlines are lime, and transitions stop under prefers-reduced-motion.

Customise it

Change --dk-bg, --dk-raise, --dk-accent, --dk-accent-ink and --dk-accent-text on .dk to recolour the whole set. Bootstrap uses the same variables on top of data-bs-theme="dark"; in Tailwind swap the #141821, #1b202b, #a3e635 and #bef264 utilities.