Dashboards
Feature flag rollout panel
Percentage targeting with environment toggles
Percentage targeting with environment toggles
Build a feature flag management panel with Tailwind CSS. The header holds the flag name in monospace with a small "boolean" type pill, a muted description line, and a prominent switch on the right showing the flag on in production. Below, three environment rows in a bordered card: each has the environment name, a small state pill, a rollout percentage with a range slider, and a muted line giving how many users that represents. Then a targeting section listing two rules as bordered rows with a condition in monospace, a small "then serve true" clause and a delete cross, plus a dashed "Add a rule" row. Finish with an audit strip of three recent changes, each with an avatar, a sentence describing the change and a relative timestamp. Then add JavaScript that keeps each percentage label and its user estimate in sync with its slider, and flips the environment pill between off, partial and full. <section class="mx-auto max-w-3xl px-6 py-12">
<div class="flex flex-wrap items-start justify-between gap-4">
<div>
<p class="flex items-center gap-2">
<span class="font-mono text-lg font-semibold text-neutral-900">checkout_v2</span>
<span class="rounded-full bg-neutral-100 px-2 py-0.5 text-[11px] font-medium text-neutral-600">boolean</span>
</p>
<p class="mt-1.5 text-sm text-neutral-500">New checkout with saved payment methods. Owner: Jules Park.</p>
</div>
<label class="flex cursor-pointer items-center gap-3">
<span class="text-sm text-neutral-600">Enabled</span>
<span class="relative inline-flex h-6 w-11 items-center rounded-full bg-neutral-900 transition">
<span class="ml-auto mr-1 h-4 w-4 rounded-full bg-white"></span>
</span>
</label>
</div>
<div class="mt-7 divide-y divide-neutral-100 overflow-hidden rounded-2xl border border-neutral-200 bg-white">
<div class="env p-5">
<div class="flex flex-wrap items-center justify-between gap-3">
<p class="flex items-center gap-2 text-sm font-medium text-neutral-900">Production <span class="pill rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-medium text-amber-700">Partial</span></p>
<p class="font-mono text-sm text-neutral-900"><span class="pct">25</span>%</p>
</div>
<input type="range" min="0" max="100" step="5" value="25" data-users="184000" class="rollout mt-3 w-full accent-neutral-900" />
<p class="users mt-1.5 text-xs text-neutral-500">Roughly 46,000 of 184,000 monthly users</p>
</div>
<div class="env p-5">
<div class="flex flex-wrap items-center justify-between gap-3">
<p class="flex items-center gap-2 text-sm font-medium text-neutral-900">Staging <span class="pill rounded-full bg-green-100 px-2 py-0.5 text-[11px] font-medium text-green-700">Full</span></p>
<p class="font-mono text-sm text-neutral-900"><span class="pct">100</span>%</p>
</div>
<input type="range" min="0" max="100" step="5" value="100" data-users="1200" class="rollout mt-3 w-full accent-neutral-900" />
<p class="users mt-1.5 text-xs text-neutral-500">Roughly 1,200 of 1,200 monthly users</p>
</div>
<div class="env p-5">
<div class="flex flex-wrap items-center justify-between gap-3">
<p class="flex items-center gap-2 text-sm font-medium text-neutral-900">Development <span class="pill rounded-full bg-green-100 px-2 py-0.5 text-[11px] font-medium text-green-700">Full</span></p>
<p class="font-mono text-sm text-neutral-900"><span class="pct">100</span>%</p>
</div>
<input type="range" min="0" max="100" step="5" value="100" data-users="40" class="rollout mt-3 w-full accent-neutral-900" />
<p class="users mt-1.5 text-xs text-neutral-500">Roughly 40 of 40 monthly users</p>
</div>
</div>
<p class="mt-8 text-xs font-semibold uppercase tracking-widest text-neutral-400">Targeting rules</p>
<div class="mt-3 space-y-2">
<div class="flex items-center gap-3 rounded-xl border border-neutral-200 bg-white px-4 py-3">
<code class="min-w-0 flex-1 truncate font-mono text-xs text-neutral-700">user.plan in ["scale", "enterprise"]</code>
<span class="shrink-0 text-xs text-neutral-500">then serve <span class="font-mono text-neutral-900">true</span></span>
<button class="shrink-0 rounded-lg p-1 text-neutral-400 transition hover:bg-neutral-100 hover:text-neutral-700" aria-label="Remove rule"><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="M6 6l12 12M18 6 6 18"/></svg></button>
</div>
<div class="flex items-center gap-3 rounded-xl border border-neutral-200 bg-white px-4 py-3">
<code class="min-w-0 flex-1 truncate font-mono text-xs text-neutral-700">user.email endsWith "@northwind.dev"</code>
<span class="shrink-0 text-xs text-neutral-500">then serve <span class="font-mono text-neutral-900">true</span></span>
<button class="shrink-0 rounded-lg p-1 text-neutral-400 transition hover:bg-neutral-100 hover:text-neutral-700" aria-label="Remove rule"><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="M6 6l12 12M18 6 6 18"/></svg></button>
</div>
<button class="flex w-full items-center gap-2 rounded-xl border border-dashed border-neutral-300 px-4 py-3 text-sm text-neutral-500 transition hover:border-neutral-400 hover:bg-neutral-50"><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 5v14M5 12h14"/></svg> Add a rule</button>
</div>
<p class="mt-8 text-xs font-semibold uppercase tracking-widest text-neutral-400">Recent changes</p>
<div class="mt-3 space-y-3">
<div class="flex items-start gap-3">
<span class="grid h-8 w-8 shrink-0 place-items-center rounded-full bg-amber-500 text-[10px] font-medium text-white">JP</span>
<p class="text-sm text-neutral-600"><span class="font-medium text-neutral-900">Jules</span> raised production from 10% to 25% <span class="text-neutral-400">· 2 hours ago</span></p>
</div>
<div class="flex items-start gap-3">
<span class="grid h-8 w-8 shrink-0 place-items-center rounded-full bg-neutral-900 text-[10px] font-medium text-white">MO</span>
<p class="text-sm text-neutral-600"><span class="font-medium text-neutral-900">Mira</span> added a targeting rule for internal accounts <span class="text-neutral-400">· yesterday</span></p>
</div>
<div class="flex items-start gap-3">
<span class="grid h-8 w-8 shrink-0 place-items-center rounded-full bg-green-600 text-[10px] font-medium text-white">DK</span>
<p class="text-sm text-neutral-600"><span class="font-medium text-neutral-900">Dana</span> enabled the flag in staging <span class="text-neutral-400">· 3 days ago</span></p>
</div>
</div>
</section> Array.prototype.slice.call(document.querySelectorAll(".env")).forEach(function (row) {
var slider = row.querySelector(".rollout");
var pct = row.querySelector(".pct");
var users = row.querySelector(".users");
var pill = row.querySelector(".pill");
var total = Number(slider.dataset.users);
function sync() {
var value = Number(slider.value);
pct.textContent = value;
users.textContent =
"Roughly " + Math.round((total * value) / 100).toLocaleString("en-US") +
" of " + total.toLocaleString("en-US") + " monthly users";
// Three states, because "on at 5%" and "on everywhere" are not the same risk.
var label = value === 0 ? "Off" : value === 100 ? "Full" : "Partial";
pill.textContent = label;
pill.className =
"pill rounded-full px-2 py-0.5 text-[11px] font-medium " +
(label === "Off" ? "bg-neutral-100 text-neutral-600" : label === "Full" ? "bg-green-100 text-green-700" : "bg-amber-100 text-amber-700");
}
slider.addEventListener("input", sync);
sync();
});