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-valuenowin sync, or the bar will announce stale progress. - The step names in
data-stepshould match the keys your backend uses.
How to use it
- Add the card to your dashboard layout, with the script after it.
- Rename the steps and set a
data-stepkey for each. - On the server, render
checkedfor completed steps and callrender()at load. - 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.