Onboarding
Brand your workspace step
Logo, colour and preview side by side
Logo, colour and preview side by side
Create a workspace branding step with Tailwind CSS in two columns. The left column is the form: a step label and heading, a workspace name field, a logo control showing a square preview tile with the current initial beside an outline "Upload" button and a muted "Remove" link plus a hint about file size, a colour picker row of eight round swatches where the selected one carries a ring, and a subdomain field with a fixed suffix rendered as a muted addon inside the input shell. The right column is a live preview: a small browser chrome with dots and an address bar showing the subdomain, then a mock app header using the chosen colour and initial, and two placeholder content rows. Then add JavaScript that mirrors the name, initial, colour and subdomain into the preview as they change, slugifies the subdomain field, and keeps the selected swatch ringed. <section class="mx-auto max-w-5xl px-6 py-12">
<div class="grid gap-12 lg:grid-cols-2">
<div>
<p class="text-xs font-medium uppercase tracking-widest text-neutral-400">Step 3 of 5</p>
<h1 class="mt-3 text-2xl font-semibold tracking-tight text-neutral-900">Make it look like yours</h1>
<p class="mt-1.5 text-sm text-neutral-500">Everything here can be changed later in settings.</p>
<div class="mt-8 space-y-6">
<div>
<label class="mb-1.5 block text-xs font-medium text-neutral-700">Workspace name</label>
<input id="brandName" type="text" value="Studioform"
class="w-full rounded-xl border border-neutral-200 px-3.5 py-2.5 text-sm outline-none transition focus:border-neutral-900 focus:ring-4 focus:ring-neutral-900/10" />
</div>
<div>
<p class="mb-1.5 text-xs font-medium text-neutral-700">Logo</p>
<div class="flex items-center gap-4">
<span id="brandLogo" class="grid h-14 w-14 place-items-center rounded-xl bg-neutral-900 text-lg font-semibold text-white">S</span>
<div>
<button class="rounded-xl border border-neutral-200 px-4 py-2 text-sm font-medium text-neutral-700 transition hover:bg-neutral-50">Upload image</button>
<button class="ml-2 text-sm text-neutral-500 underline-offset-4 hover:text-neutral-900 hover:underline">Remove</button>
<p class="mt-1.5 text-xs text-neutral-400">PNG or SVG, square, up to 1 MB.</p>
</div>
</div>
</div>
<div>
<p class="mb-2.5 text-xs font-medium text-neutral-700">Accent colour</p>
<div id="brandSwatches" class="flex flex-wrap gap-2.5">
<button data-color="#171717" class="swatch h-9 w-9 rounded-full bg-neutral-900 ring-2 ring-neutral-900 ring-offset-2"></button>
<button data-color="#16a34a" class="swatch h-9 w-9 rounded-full bg-green-600"></button>
<button data-color="#2563eb" class="swatch h-9 w-9 rounded-full bg-blue-600"></button>
<button data-color="#f59e0b" class="swatch h-9 w-9 rounded-full bg-amber-500"></button>
<button data-color="#dc2626" class="swatch h-9 w-9 rounded-full bg-red-600"></button>
<button data-color="#7c3aed" class="swatch h-9 w-9 rounded-full bg-violet-600"></button>
<button data-color="#0d9488" class="swatch h-9 w-9 rounded-full bg-teal-600"></button>
<button data-color="#525252" class="swatch h-9 w-9 rounded-full bg-neutral-600"></button>
</div>
</div>
<div>
<label class="mb-1.5 block text-xs font-medium text-neutral-700">Workspace address</label>
<div class="flex items-center rounded-xl border border-neutral-200 transition focus-within:border-neutral-900 focus-within:ring-4 focus-within:ring-neutral-900/10">
<input id="brandSlug" type="text" value="studioform"
class="min-w-0 flex-1 rounded-l-xl px-3.5 py-2.5 text-sm outline-none" />
<span class="shrink-0 rounded-r-xl border-l border-neutral-200 bg-neutral-50 px-3.5 py-2.5 text-sm text-neutral-400">.northwind.app</span>
</div>
</div>
</div>
<div class="mt-9 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 for now</button>
<button class="rounded-xl bg-neutral-900 px-6 py-2.5 text-sm font-medium text-white transition hover:bg-neutral-800">Continue</button>
</div>
</div>
<div class="lg:pt-16">
<div class="overflow-hidden rounded-2xl border border-neutral-200 bg-white shadow-sm">
<div class="flex items-center gap-3 border-b border-neutral-200 bg-neutral-50 px-4 py-2.5">
<span class="flex gap-1.5">
<span class="h-2.5 w-2.5 rounded-full bg-neutral-300"></span>
<span class="h-2.5 w-2.5 rounded-full bg-neutral-300"></span>
<span class="h-2.5 w-2.5 rounded-full bg-neutral-300"></span>
</span>
<span id="previewUrl" class="flex-1 truncate rounded-md bg-white px-2.5 py-1 font-mono text-[11px] text-neutral-400 ring-1 ring-neutral-200">studioform.northwind.app</span>
</div>
<div id="previewHeader" class="flex items-center gap-3 px-5 py-4" style="background:#171717">
<span id="previewLogo" class="grid h-8 w-8 place-items-center rounded-lg bg-white/15 text-sm font-semibold text-white">S</span>
<span id="previewName" class="text-sm font-medium text-white">Studioform</span>
<span class="ml-auto h-7 w-7 rounded-full bg-white/20"></span>
</div>
<div class="space-y-3 p-5">
<div class="h-3 w-2/3 rounded-full bg-neutral-100"></div>
<div class="h-24 rounded-xl border border-neutral-200 bg-neutral-50"></div>
<div class="h-3 w-1/2 rounded-full bg-neutral-100"></div>
<div class="h-16 rounded-xl border border-neutral-200 bg-neutral-50"></div>
</div>
</div>
<p class="mt-3 text-center text-xs text-neutral-400">Live preview — this is what your team will see.</p>
</div>
</div>
</section> var name = document.getElementById("brandName");
var slug = document.getElementById("brandSlug");
var logo = document.getElementById("brandLogo");
var swatches = Array.prototype.slice.call(document.querySelectorAll(".swatch"));
var previewName = document.getElementById("previewName");
var previewLogo = document.getElementById("previewLogo");
var previewHeader = document.getElementById("previewHeader");
var previewUrl = document.getElementById("previewUrl");
function initial(value) {
var trimmed = value.trim();
return trimmed ? trimmed.charAt(0).toUpperCase() : "?";
}
name.addEventListener("input", function () {
previewName.textContent = name.value || "Untitled workspace";
logo.textContent = initial(name.value);
previewLogo.textContent = initial(name.value);
});
slug.addEventListener("input", function () {
// Keep the field itself honest rather than validating it later.
slug.value = slug.value.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-");
previewUrl.textContent = (slug.value || "workspace") + ".northwind.app";
});
swatches.forEach(function (swatch) {
swatch.addEventListener("click", function () {
swatches.forEach(function (s) {
s.classList.remove("ring-2", "ring-neutral-900", "ring-offset-2");
});
swatch.classList.add("ring-2", "ring-neutral-900", "ring-offset-2");
previewHeader.style.background = swatch.dataset.color;
logo.style.background = swatch.dataset.color;
});
});