Componenteditors1.16 KB gzipZero dependencies
Feature Flag Panel
Feature flag panel with toggle switches and rollout percentages.
Preview
Live Preview
Dark mode
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add feature-flag-panelOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { FeatureFlagPanel } from "@/components/ui/feature-flag-panel";
<FeatureFlagPanel />Usage guidance
Use Feature Flag Panel when a ticket stub surface needs Feature flag panel with toggle switches and rollout percentages. Keep props minimal and prefer composition with sibling stubs.
Avoid Feature Flag Panel for long form flows or dense data grids where a dedicated layout block fits better.
Source code
feature-flag-panel.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
export interface FeatureFlag {
id: string;
label: string;
enabled: boolean;
/** Gradual rollout percentage */
rollout?: number;
}
export interface FeatureFlagPanelProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onToggle"> {
flags: FeatureFlag[];
onToggle?: (id: string, enabled: boolean) => void;
}
/** Feature flag management panel: toggles + rollout percentages. */
export function FeatureFlagPanel({ flags, onToggle, className, ...props }: FeatureFlagPanelProps) {
const [state, setState] = React.useState(flags);
const toggle = (id: string) => {
const next = state.map((f) => (f.id === id ? { ...f, enabled: !f.enabled } : f));
setState(next);
const changed = next.find((f) => f.id === id);
if (changed) onToggle?.(changed.id, changed.enabled);
};
return (
<div className={cn("w-full max-w-md divide-y divide-border/60 rounded-lg border border-border bg-card", className)} {...props}>
<style>{`@keyframes ffSlide { from { opacity: 0; transform: translateX(-6px); } to { opacity: 1; transform: translateX(0); } }`}</style>
{state.map((f, idx) => (
<div key={f.id} className="flex items-center gap-3 px-4 py-3 animate-[ffSlide_0.25s_ease-out_both] motion-reduce:animate-none" style={{ animationDelay: `${idx * 50}ms` }}>
<div className="min-w-0 flex-1">
<p className="text-sm font-medium">{f.label}</p>
{f.rollout !== undefined && (
<div className="mt-1 flex items-center gap-2">
<div className="h-1 w-20 overflow-hidden rounded-full bg-border/60">
<div className="h-full rounded-full bg-accent transition-all duration-500 motion-reduce:transition-none" style={{ width: `${f.rollout}%` }} />
</div>
<span className="font-mono text-[9px] tabular-nums text-muted-foreground">%{f.rollout} rollout</span>
</div>
)}
</div>
<button
role="switch"
aria-checked={f.enabled}
aria-label={f.label}
onClick={() => toggle(f.id)}
className={cn(
"relative h-5 w-9 shrink-0 rounded-full border transition-colors duration-200 motion-reduce:transition-none",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
f.enabled ? "border-accent bg-accent" : "border-border bg-secondary"
)}
>
<span
className={cn(
"absolute top-0.5 size-3.5 rounded-full bg-background shadow-xs transition-all duration-200 motion-reduce:transition-none",
f.enabled ? "left-[18px]" : "left-0.5"
)}
/>
</button>
</div>
))}
</div>
);
}
Props
| Prop | Type | Description |
|---|---|---|
| flags | FeatureFlag[] | |
| onToggle | (id: string, enabled: boolean) => void |