Onboarding
Install the CLI step
Copyable commands per operating system
Copyable commands per operating system
Build a developer onboarding step with Tailwind CSS for installing a command-line tool. The header has a step label, a heading and a muted line naming the current version and size. Below, a segmented control of three tabs — macOS, Linux and Windows — then a dark rounded-xl command block showing one or two monospaced lines with a leading prompt symbol in a dimmer colour and a copy button in the corner. Under it, a "Then verify" block with a second smaller command and the expected output shown in muted monospace. Then a three-item checklist that fills in as steps complete, each with a circle that becomes a green check. Finish with a footer of a ghost "Skip, I use Docker" button and a dark Continue that is disabled until the verification step is marked done. Then add JavaScript that switches command sets per platform, copies to the clipboard with confirmation, marks checklist items complete on copy, and enables Continue when all are done. <section class="mx-auto max-w-2xl px-6 py-12">
<p class="text-xs font-medium uppercase tracking-widest text-neutral-400">Step 2 of 4</p>
<h1 class="mt-3 text-2xl font-semibold tracking-tight text-neutral-900">Install the command line tool</h1>
<p class="mt-1.5 text-sm text-neutral-500">Version 4.2.1 · 18 MB · no daemon, no background process.</p>
<div id="osTabs" class="mt-7 inline-flex rounded-xl border border-neutral-200 p-1">
<button data-os="mac" class="os-tab rounded-lg bg-neutral-900 px-4 py-2 text-sm font-medium text-white">macOS</button>
<button data-os="linux" class="os-tab rounded-lg px-4 py-2 text-sm font-medium text-neutral-600 transition hover:text-neutral-900">Linux</button>
<button data-os="win" class="os-tab rounded-lg px-4 py-2 text-sm font-medium text-neutral-600 transition hover:text-neutral-900">Windows</button>
</div>
<div class="relative mt-4 rounded-xl bg-neutral-900 p-5">
<button id="copyInstall" class="absolute right-3 top-3 rounded-lg border border-neutral-700 px-2.5 py-1 text-[11px] font-medium text-neutral-300 transition hover:bg-neutral-800">Copy</button>
<pre id="installCmd" class="overflow-x-auto pr-16 font-mono text-sm leading-relaxed text-neutral-200"></pre>
</div>
<p class="mt-6 text-xs font-semibold uppercase tracking-widest text-neutral-400">Then verify</p>
<div class="relative mt-2 rounded-xl bg-neutral-900 p-5">
<button id="copyVerify" class="absolute right-3 top-3 rounded-lg border border-neutral-700 px-2.5 py-1 text-[11px] font-medium text-neutral-300 transition hover:bg-neutral-800">Copy</button>
<pre class="overflow-x-auto pr-16 font-mono text-sm leading-relaxed text-neutral-200"><span class="text-neutral-600">$</span> northwind --version</pre>
<p class="mt-3 border-t border-neutral-800 pt-3 font-mono text-xs text-neutral-500">northwind/4.2.1 darwin-arm64</p>
</div>
<div class="mt-7 space-y-3">
<p class="check flex items-center gap-3 text-sm text-neutral-500" data-key="install">
<span class="mark grid h-5 w-5 place-items-center rounded-full border border-neutral-300 text-[10px] text-transparent"><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="m5 12.5 4.25 4.25L19 7"/></svg></span> Install command copied
</p>
<p class="check flex items-center gap-3 text-sm text-neutral-500" data-key="verify">
<span class="mark grid h-5 w-5 place-items-center rounded-full border border-neutral-300 text-[10px] text-transparent"><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="m5 12.5 4.25 4.25L19 7"/></svg></span> Verify command copied
</p>
<p class="check flex items-center gap-3 text-sm text-neutral-500" data-key="run">
<span class="mark grid h-5 w-5 place-items-center rounded-full border border-neutral-300 text-[10px] text-transparent"><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="m5 12.5 4.25 4.25L19 7"/></svg></span> Version printed in your terminal
</p>
</div>
<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-600 transition hover:bg-neutral-50">Skip, I use Docker</button>
<button id="cliContinue" 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 COMMANDS = {
mac: "brew install northwind/tap/cli",
linux: "curl -fsSL https://get.northwind.dev | sh",
win: "winget install Northwind.CLI",
};
var tabs = Array.prototype.slice.call(document.querySelectorAll(".os-tab"));
var block = document.getElementById("installCmd");
var checks = Array.prototype.slice.call(document.querySelectorAll(".check"));
var next = document.getElementById("cliContinue");
var done = {};
var current = "mac";
function paint() {
block.innerHTML = '<span class="text-neutral-600">$</span> ' + COMMANDS[current];
tabs.forEach(function (tab) {
var on = tab.dataset.os === current;
tab.classList.toggle("bg-neutral-900", on);
tab.classList.toggle("text-white", on);
tab.classList.toggle("text-neutral-600", !on);
});
}
function complete(key) {
done[key] = true;
checks.forEach(function (check) {
if (!done[check.dataset.key]) return;
check.classList.remove("text-neutral-500");
check.classList.add("text-neutral-900");
var mark = check.querySelector(".mark");
mark.className = "mark grid h-5 w-5 place-items-center rounded-full bg-green-100 text-[10px] text-green-700";
});
// Copying both commands is taken as having run them — this is a demo, after all.
if (done.install && done.verify) complete2();
}
function complete2() {
if (done.run) return;
done.run = true;
var last = checks[2];
last.classList.remove("text-neutral-500");
last.classList.add("text-neutral-900");
last.querySelector(".mark").className = "mark grid h-5 w-5 place-items-center rounded-full bg-green-100 text-[10px] text-green-700";
next.disabled = false;
next.classList.remove("opacity-40", "cursor-not-allowed");
next.classList.add("hover:bg-neutral-800");
}
function copyButton(button, getText, key) {
button.addEventListener("click", function () {
navigator.clipboard.writeText(getText()).then(function () {
button.textContent = "Copied";
setTimeout(function () {
button.textContent = "Copy";
}, 1500);
complete(key);
});
});
}
tabs.forEach(function (tab) {
tab.addEventListener("click", function () {
current = tab.dataset.os;
paint();
});
});
copyButton(document.getElementById("copyInstall"), function () {
return COMMANDS[current];
}, "install");
copyButton(document.getElementById("copyVerify"), function () {
return "northwind --version";
}, "verify");
paint();