React + MotionFilters
Segmented control
A row of options where a colored pill slides over to whichever one you pick.
Settings
3
Code
import { useId, useState } from "react";
import { motion } from "framer-motion";
const sizeMap = {
sm: { pad: "px-3 py-1.5", text: "text-xs" },
md: { pad: "px-4 py-2", text: "text-sm" },
lg: { pad: "px-5 py-2.5", text: "text-base" },
} as const;
interface SegmentedControlProps {
accent?: string;
size?: keyof typeof sizeMap;
options?: string[];
value?: string;
onChange?: (value: string) => void;
}
export default function SegmentedControl({
accent = "#0071e3",
size = "md",
options = ["Design", "Code", "Audio"],
value,
onChange,
}: SegmentedControlProps) {
const [internal, setInternal] = useState(options[0] ?? "");
const layoutId = useId();
const active = value ?? internal;
const current = options.includes(active) ? active : options[0];
const s = sizeMap[size];
const select = (o: string) => {
if (value === undefined) setInternal(o);
onChange?.(o);
};
return (
<div className="flex rounded-full bg-black/5 p-1 dark:bg-white/10">
{options.map((o) => (
<button
key={o}
type="button"
aria-pressed={current === o}
onClick={() => select(o)}
className={`relative rounded-full font-medium ${s.pad} ${s.text}`}
>
{current === o && (
<motion.span
layoutId={layoutId}
className="absolute inset-0 rounded-full"
style={{ backgroundColor: accent }}
transition={{ type: "spring", stiffness: 380, damping: 30 }}
/>
)}
<span
className="relative z-10"
style={{ color: current === o ? "#ffffff" : undefined }}
>
{o}
</span>
</button>
))}
</div>
);
}
Prompt for AI
Paste it into ChatGPT, Claude or Cursor and it will adapt the block to your project with these settings, even if you don’t use React.
More in Filters
Where to use it
A segmented control is for switching views, not for filtering: “Monthly / Yearly” on a pricing page, “List / Map” in a property search, or “Day / Week / Month” in a calendar. Because the pill slides instead of jumping, people see what changed. Reach for this segmented control when there are two to four choices.
It gets cramped beyond four options, especially on phones, so trim the list or switch to tabs. Pick an accent with enough contrast for white text, since the selected label turns white.