Skip to content
Notra logoNotra UI
Esc
↑↓navigate↵open⌘Jpreview

Icon Tabs

A segmented tab selector where only the active tab shows its icon.

"use client";

import { LinkIcon, SendIcon } from "lucide-react";
import { useState } from "react";

import {
  IconTabs,
  IconTabsList,
  IconTabsTrigger,
} from "../components/icon-tabs";

export default function IconTabsExample() {
  const [tab, setTab] = useState("deliveries");

  return (
    <IconTabs className="items-center p-6" onValueChange={setTab} value={tab}>
      <IconTabsList aria-label="Webhook views" className="w-64" value={tab}>
        <IconTabsTrigger icon={<SendIcon size={15} />} value="deliveries">
          Deliveries
        </IconTabsTrigger>
        <IconTabsTrigger icon={<LinkIcon size={15} />} value="endpoints">
          Endpoints
        </IconTabsTrigger>
      </IconTabsList>
    </IconTabs>
  );
}
$ bunx --bun shadcn@latest add @notra/icon-tabs

Inactive tabs stay text-only. On the active tab the icon widens and fades in beside the label, and the indicator follows the resize frame by frame, so it lands with the tab in one motion. It is built on Base UI’s Tabs, so keyboard navigation and screen reader support come with it. Reduced motion turns the slide off.

const [tab, setTab] = useState("deliveries");

<IconTabs onValueChange={setTab} value={tab}>
  <IconTabsList aria-label="Webhook views" value={tab}>
    <IconTabsTrigger icon={<SendIcon size={15} />} value="deliveries">
      Deliveries
    </IconTabsTrigger>
    <IconTabsTrigger icon={<LinkIcon size={15} />} value="endpoints">
      Endpoints
    </IconTabsTrigger>
  </IconTabsList>
  <IconTabsContent value="deliveries">...</IconTabsContent>
  <IconTabsContent value="endpoints">...</IconTabsContent>
</IconTabs>

The list is as wide as its tabs, so it grows by one icon whenever the active tab changes. To center it without it shifting sideways, give the list a fixed width, like className="w-64". The tabs then share the width equally.

IconTabsList needs the active value as well, so the indicator knows when to start sliding.

Prop On Description
value IconTabsList The active tab. Must match the value of IconTabs.
icon IconTabsTrigger Shown only on the active tab, sliding in beside the label.
iconPinned IconTabsTrigger Keeps the icon visible on inactive tabs, e.g. for a live spinner.

Every other Base UI Tabs prop is passed through, except orientation: the tabs are horizontal only.