Onboarding
Goal picker with chips
Multi-select intent that shapes the setup
Multi-select intent that shapes the setup
Build a goal selection onboarding step with Tailwind CSS. Centre a max-w-2xl column with a step label, a heading asking what the person wants to do first, and a muted line saying they can pick more than one. Below, a grid of nine selectable chips laid out two or three per row: each is a bordered rounded-xl button with a glyph tile, a short label and a muted one-line description, and when selected it gains a neutral-900 border, a soft neutral-50 background and a small check badge in the corner. Under the grid, a live line reading how many are selected with a minimum of one, and a footer with a ghost "Skip" and a dark Continue button that stays disabled until at least one chip is chosen. Then add JavaScript that toggles selection, caps the choice at four with a small inline note when the cap is reached, and keeps the counter and button state in sync. <section class="mx-auto max-w-2xl px-6 py-14">
<p class="text-xs font-medium uppercase tracking-widest text-neutral-400">Step 2 of 5</p>
<h1 class="mt-3 text-2xl font-semibold tracking-tight text-neutral-900">What do you want to do first?</h1>
<p class="mt-1.5 text-sm text-neutral-500">Pick up to four. We will put those tools in front and hide the rest for now.</p>
<div id="goalGrid" class="mt-8 grid gap-3 sm:grid-cols-2">
<button data-goal="dashboards" class="goal relative rounded-xl border border-neutral-200 bg-white p-4 text-left transition hover:border-neutral-300">
<span class="grid h-9 w-9 place-items-center rounded-lg bg-neutral-100 text-neutral-700"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><path d="M4 20V10m5 10V4m6 16v-7m5 7V7"/></svg></span>
<span class="mt-3 block text-sm font-medium text-neutral-900">Build dashboards</span>
<span class="mt-0.5 block text-xs text-neutral-500">Charts your team can read without you</span>
</button>
<button data-goal="alerts" class="goal relative rounded-xl border border-neutral-200 bg-white p-4 text-left transition hover:border-neutral-300">
<span class="grid h-9 w-9 place-items-center rounded-lg bg-neutral-100 text-neutral-700"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m4 7 8 6 8-6"/></svg></span>
<span class="mt-3 block text-sm font-medium text-neutral-900">Set up alerts</span>
<span class="mt-0.5 block text-xs text-neutral-500">Know before a customer tells you</span>
</button>
<button data-goal="import" class="goal relative rounded-xl border border-neutral-200 bg-white p-4 text-left transition hover:border-neutral-300">
<span class="grid h-9 w-9 place-items-center rounded-lg bg-neutral-100 text-neutral-700"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><path d="M12 16V4m0 0L7 9m5-5 5 5"/><path d="M5 14v5a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-5"/></svg></span>
<span class="mt-3 block text-sm font-medium text-neutral-900">Import existing data</span>
<span class="mt-0.5 block text-xs text-neutral-500">CSV, warehouse or a live connection</span>
</button>
<button data-goal="api" class="goal relative rounded-xl border border-neutral-200 bg-white p-4 text-left transition hover:border-neutral-300">
<span class="grid h-9 w-9 place-items-center rounded-lg bg-neutral-100 text-neutral-700"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><path d="m8 9-3 3 3 3m8-6 3 3-3 3m-2-9-4 12"/></svg></span>
<span class="mt-3 block text-sm font-medium text-neutral-900">Work through the API</span>
<span class="mt-0.5 block text-xs text-neutral-500">Keys, SDKs and webhook setup</span>
</button>
<button data-goal="collaborate" class="goal relative rounded-xl border border-neutral-200 bg-white p-4 text-left transition hover:border-neutral-300">
<span class="grid h-9 w-9 place-items-center rounded-lg bg-neutral-100 text-neutral-700"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg></span>
<span class="mt-3 block text-sm font-medium text-neutral-900">Invite my team</span>
<span class="mt-0.5 block text-xs text-neutral-500">Roles, permissions and shared views</span>
</button>
<button data-goal="compliance" class="goal relative rounded-xl border border-neutral-200 bg-white p-4 text-left transition hover:border-neutral-300">
<span class="grid h-9 w-9 place-items-center rounded-lg bg-neutral-100 text-neutral-700"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><path d="M12 3 4.5 6v5.5c0 4.7 3.2 7.7 7.5 9.5 4.3-1.8 7.5-4.8 7.5-9.5V6L12 3Z"/><path d="m9 12 2 2 4-4"/></svg></span>
<span class="mt-3 block text-sm font-medium text-neutral-900">Satisfy an audit</span>
<span class="mt-0.5 block text-xs text-neutral-500">Evidence, retention and access logs</span>
</button>
<button data-goal="automate" class="goal relative rounded-xl border border-neutral-200 bg-white p-4 text-left transition hover:border-neutral-300">
<span class="grid h-9 w-9 place-items-center rounded-lg bg-neutral-100 text-neutral-700"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><path d="M20 11a8 8 0 1 0-2.34 5.66"/><path d="M20 4v7h-7"/></svg></span>
<span class="mt-3 block text-sm font-medium text-neutral-900">Automate a workflow</span>
<span class="mt-0.5 block text-xs text-neutral-500">Replace a script nobody wants to own</span>
</button>
<button data-goal="migrate" class="goal relative rounded-xl border border-neutral-200 bg-white p-4 text-left transition hover:border-neutral-300">
<span class="grid h-9 w-9 place-items-center rounded-lg bg-neutral-100 text-neutral-700"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><path d="M7 7h12m0 0-3-3m3 3-3 3M17 17H5m0 0 3 3m-3-3 3-3"/></svg></span>
<span class="mt-3 block text-sm font-medium text-neutral-900">Migrate from another tool</span>
<span class="mt-0.5 block text-xs text-neutral-500">We will map the concepts across</span>
</button>
<button data-goal="explore" class="goal relative rounded-xl border border-neutral-200 bg-white p-4 text-left transition hover:border-neutral-300">
<span class="grid h-9 w-9 place-items-center rounded-lg bg-neutral-100 text-neutral-700"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><circle cx="11" cy="11" r="7"/><path d="m16 16 4 4"/></svg></span>
<span class="mt-3 block text-sm font-medium text-neutral-900">Just having a look</span>
<span class="mt-0.5 block text-xs text-neutral-500">Sample data, nothing to connect</span>
</button>
</div>
<p id="goalCounter" class="mt-5 text-xs text-neutral-400">Nothing selected yet — pick at least one.</p>
<div class="mt-8 flex items-center justify-between border-t border-neutral-200 pt-5">
<button class="rounded-xl px-4 py-2.5 text-sm font-medium text-neutral-700 transition hover:bg-neutral-50">Skip</button>
<button id="goalContinue" disabled class="cursor-not-allowed rounded-xl bg-neutral-900 px-6 py-2.5 text-sm font-medium text-white opacity-40 transition">Continue</button>
</div>
</section> var MAX = 4;
var goals = Array.prototype.slice.call(document.querySelectorAll(".goal"));
var counter = document.getElementById("goalCounter");
var next = document.getElementById("goalContinue");
var chosen = [];
function badge(el) {
var mark = document.createElement("span");
mark.className = "goal-check absolute right-3 top-3 grid h-5 w-5 place-items-center rounded-full bg-neutral-900 text-[10px] text-white";
mark.textContent = "✓";
el.appendChild(mark);
}
function sync() {
goals.forEach(function (g) {
var on = chosen.indexOf(g.dataset.goal) !== -1;
g.classList.toggle("border-neutral-900", on);
g.classList.toggle("bg-neutral-50", on);
g.classList.toggle("border-neutral-200", !on);
var mark = g.querySelector(".goal-check");
if (on && !mark) badge(g);
if (!on && mark) mark.remove();
});
if (chosen.length === 0) counter.textContent = "Nothing selected yet — pick at least one.";
else if (chosen.length === MAX) counter.textContent = chosen.length + " of " + MAX + " selected · that is the maximum, deselect one to swap.";
else counter.textContent = chosen.length + " of " + MAX + " selected.";
var ok = chosen.length > 0;
next.disabled = !ok;
next.classList.toggle("opacity-40", !ok);
next.classList.toggle("cursor-not-allowed", !ok);
}
goals.forEach(function (goal) {
goal.addEventListener("click", function () {
var key = goal.dataset.goal;
var at = chosen.indexOf(key);
if (at !== -1) chosen.splice(at, 1);
else if (chosen.length < MAX) chosen.push(key);
sync();
});
});
sync();