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.