DEV3 Technologies home

Onboarding checklist with progress bar

A small getting-started card with checkbox steps, a live progress bar, an accessible progressbar role and a completion message.

Tailwind CSSDashboard widgetsonboardingchecklistprogresswidget

Live preview

Code

<aside class="w-full max-w-sm rounded-2xl border border-slate-200 bg-white p-4 shadow-sm" aria-labelledby="ob-title">
  <div class="flex items-center justify-between">
    <h2 id="ob-title" class="font-semibold text-slate-900">Get started</h2>
    <span id="ob-count" class="text-xs font-medium text-slate-500">0 of 4</span>
  </div>
  <div class="mt-2 h-2 overflow-hidden rounded-full bg-slate-100" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" id="ob-bar">
    <div id="ob-fill" class="h-full w-0 rounded-full bg-emerald-500 transition-all duration-300"></div>
  </div>

  <ul class="mt-3 space-y-1" id="ob-list">
    <li><label class="flex cursor-pointer items-start gap-3 rounded-lg p-2 hover:bg-slate-50">
      <input type="checkbox" data-step="profile" class="peer mt-0.5 size-4 accent-emerald-600">
      <span class="text-sm text-slate-700 peer-checked:text-slate-400 peer-checked:line-through">Complete your profile</span></label></li>
    <li><label class="flex cursor-pointer items-start gap-3 rounded-lg p-2 hover:bg-slate-50">
      <input type="checkbox" data-step="site" class="peer mt-0.5 size-4 accent-emerald-600">
      <span class="text-sm text-slate-700 peer-checked:text-slate-400 peer-checked:line-through">Create your first site</span></label></li>
    <li><label class="flex cursor-pointer items-start gap-3 rounded-lg p-2 hover:bg-slate-50">
      <input type="checkbox" data-step="domain" class="peer mt-0.5 size-4 accent-emerald-600">
      <span class="text-sm text-slate-700 peer-checked:text-slate-400 peer-checked:line-through">Connect a domain</span></label></li>
    <li><label class="flex cursor-pointer items-start gap-3 rounded-lg p-2 hover:bg-slate-50">
      <input type="checkbox" data-step="publish" class="peer mt-0.5 size-4 accent-emerald-600">
      <span class="text-sm text-slate-700 peer-checked:text-slate-400 peer-checked:line-through">Publish</span></label></li>
  </ul>
  <p id="ob-done" class="mt-3 hidden rounded-lg bg-emerald-50 p-2 text-center text-sm text-emerald-700">All set. Nice work!</p>
</aside>

<script>
  const boxes = [...document.querySelectorAll('#ob-list input')];
  function render() {
    const done = boxes.filter((b) => b.checked).length;
    const pct = Math.round((done / boxes.length) * 100);
    document.getElementById('ob-count').textContent = `${done} of ${boxes.length}`;
    document.getElementById('ob-fill').style.width = pct + '%';
    document.getElementById('ob-bar').setAttribute('aria-valuenow', pct);
    document.getElementById('ob-done').classList.toggle('hidden', done !== boxes.length);
  }
  boxes.forEach((b) => b.addEventListener('change', render));
  // Server hook: mark steps completed from real events, then call render()
  // e.g. markDone('profile') after a successful save.
  window.markDone = (step) => { boxes.find((b) => b.dataset.step === step).checked = true; render(); };
</script>

How it works

What it solves

New users stay longer when the first session gives them a short list of concrete wins. This card shows the next steps, how far they have come and a small reward at the end, in a footprint that fits a dashboard sidebar.

How it works

Each step is a label wrapping a native checkbox, so clicking the text toggles it and keyboard use is built in. The strike-through styling comes from peer-checked, which targets the text span that follows the input, so no extra JavaScript is needed for visuals. A single render function recounts checked boxes and updates four things: the "2 of 4" label, the bar width, the aria-valuenow value on the progressbar element and the visibility of the completion message.

The page can also push real progress: markDone('site') checks a step programmatically and re-renders, so you can call it when the server confirms the matching event rather than trusting only manual clicks.

Adapting it

  • Persist each step on the server and render checked boxes on page load, then call render() once.
  • Add or remove list items; the code uses the box count, not a fixed number.
  • Hide the card entirely for users who finished every step.

Pitfalls

  • A purely client-side checklist can be ticked without doing the task; derive truth from your data when it matters.
  • Keep aria-valuenow in sync, or the bar will announce stale progress.
  • The step names in data-step should match the keys your backend uses.

How to use it

  1. Add the card to your dashboard layout, with the script after it.
  2. Rename the steps and set a data-step key for each.
  3. On the server, render checked for completed steps and call render() at load.
  4. Call markDone(key) from your own code when a related action succeeds.

Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.

Related snippets