prince109 · No 109

Expandable tabs

Icon tab bar where the active tab expands to reveal its label.

Install

pnpm dlx shadcn add @prince-ui/prince109

Dependencies

framer-motionlucide-react

Try it

  • —Click any icon — the whole bar re-flows as that tab's label opens.
  • —Move between two tabs quickly to watch the layout spring interrupt and redirect itself.
  • —Watch the tab you leave: its label disappears rather than closing.

How to use

Edit the TABS array at the top of the file. Each entry pairs a lucide icon with the label that appears while its tab is active — that array is the whole configuration.

The layout prop on each button does the work. Framer Motion measures every button before and after the label mounts and springs the widths between the two, so nothing is hard-coded and labels of any length just work.

Basic usage

import { ExpandableTabs } from "@/components/ui/prince109";

export default function Example() {
  return <ExpandableTabs />;
}

Driving the selection from outside

Lift the active index when the bar has to stay in step with a router or a parent panel.

export function ExpandableTabs({
  active,
  onSelect,
}: {
  active: number;
  onSelect: (index: number) => void;
}) {
  return (
    // ...same markup, with the internal useState removed
    // and each button calling `onSelect(idx)`
  );
}

Props

TABS entry

One item in the TABS array. The component itself takes no props — it is configured by editing that array.

PropDescription
iconAny lucide-react icon component, rendered at h-4 w-4. Passed as the component itself, not an element.
labelText revealed while the tab is active, and the key React tracks the button by. Keep it short — the bar springs open far enough to fit whatever you put here.

Notes

  • —The label mounts only while its tab is active, so width 0 → auto animates it open but there is no exit — wrap it in AnimatePresence if you want it to close rather than vanish.
  • —overflow-hidden and whitespace-nowrap on the label are what stop the text reflowing while its width animates.
  • —Active colours change through transition-colors, so they cross-fade on the CSS timeline rather than the layout spring's.
  • —These are plain buttons — no role="tab", no arrow-key handling. Add both if the bar is real navigation rather than a demo.

Source code

The whole component is one file. Open it with the </> control at the top right of the preview, or take it straight from here.

Keep in mind

Several of these are recreations of interactions I admired elsewhere, rebuilt from scratch and usually extended. I have credited what I could trace — if something of yours is here uncredited, tell me and I will fix it.

License & usage

  • —Free to use and modify in personal and commercial projects.
  • —MIT licensed — no attribution required, though a link back is always welcome.
  • —No paywalled tier, no pro version, no lock-in.

Contact

Found a bug, or built something with it? Send me a note.

PreviousAnimated counter
pnpm dlx shadcn add @prince-ui/prince109
Prince UI·Components·Expandable tabs