hiip/ui
14 items

Components / Controls

Tabs

Pill or underline tabs. A panel is not mounted until its tab is opened.

Cards, grouped by column
0 orders — Order created
demos.tsx
import {
  CancelCircleIcon,
  CheckmarkCircle02Icon,
  DashboardSquare01Icon,
  DeliveryTruck01Icon,
  Loading03Icon,
  Menu01Icon,
  Package01Icon,
} from "@hugeicons/core-free-icons";
import {
  Tabs,
  TabsContent,
  TabsList,
  TabsTrigger,
} from "@/components/ui/hiip-ui/tabs";

// Counts are the point of this one: a status filter is unusable without them,
// because "Delivery failed" reads the same whether it holds 3 rows or none.
// Typed off `TabsTrigger` itself rather than `as const`: only some of these
// rows carry an icon or a tone, and a const-asserted union has no `icon` at all
// on the ones that don't — which is a type error at the spread, not a fact
// about the data.
const SHIPPING: {
  value: string;
  label: string;
  icon?: React.ComponentProps<typeof TabsTrigger>["icon"];
  tone?: React.ComponentProps<typeof TabsTrigger>["tone"];
  count: number;
}[] = [
  { value: "all", label: "All", count: 159 },
  {
    value: "not-shipped",
    label: "Not shipped",
    icon: Loading03Icon,
    count: 24,
  },
  {
    value: "created",
    label: "Order created",
    icon: Package01Icon,
    tone: "info",
    count: 0,
  },
  {
    value: "transit",
    label: "In transit",
    icon: DeliveryTruck01Icon,
    tone: "warning",
    count: 4,
  },
  {
    value: "delivered",
    label: "Delivered",
    icon: CheckmarkCircle02Icon,
    tone: "success",
    count: 128,
  },
  {
    value: "failed",
    label: "Delivery failed",
    icon: CancelCircleIcon,
    tone: "danger",
    count: 3,
  },
];

const PANEL =
  "rounded-xl border border-dashed p-6 text-center text-sm text-muted-foreground";

function TabsDemo() {
  return (
    <div className="flex w-full flex-col gap-8">
      {/* The pill: a control inside a toolbar, switching a view of one thing. */}
      <Tabs defaultSelectedKey="board" className="gap-3">
        <TabsList className="self-center">
          <TabsTrigger id="board" icon={DashboardSquare01Icon}>
            Board
          </TabsTrigger>
          <TabsTrigger id="table" icon={Menu01Icon}>
            Table
          </TabsTrigger>
        </TabsList>
        <TabsContent id="board" className={PANEL}>
          Cards, grouped by column
        </TabsContent>
        <TabsContent id="table" className={PANEL}>
          Rows, sorted by date
        </TabsContent>
      </Tabs>

      {/* The underline: page-level sections, with a count on each. Wide enough
          to overflow on a phone, so the list scrolls rather than widening the
          page. */}
      <Tabs defaultSelectedKey="created" className="gap-3">
        <div className="max-w-full overflow-x-auto">
          <TabsList variant="line" className="w-max">
            {SHIPPING.map((s) => (
              <TabsTrigger
                key={s.value}
                id={s.value}
                icon={s.icon}
                tone={s.tone}
                count={s.count}
              >
                {s.label}
              </TabsTrigger>
            ))}
          </TabsList>
        </div>
        {SHIPPING.map((s) => (
          <TabsContent key={s.value} id={s.value} className={PANEL}>
            {s.count} {s.count === 1 ? "order" : "orders"} — {s.label}
          </TabsContent>
        ))}
      </Tabs>
    </div>
  );
}

Install — needs @hiip registered in components.json

bunx shadcn@latest add @hiip/tabs

Or by URL, with the token in the query

bunx shadcn@latest add "https://ui.hiip.ai/r/tabs.json?token=$HIIP_REGISTRY_TOKEN"

Both need a token — set one up.

Anatomy

Import from components/ui/hiip-ui/tabs.

<Tabs defaultSelectedKey="board">
  <TabsList variant="pill">        {/* or "line" */}
    <TabsTrigger id="board" icon={…} count={12}>Board</TabsTrigger>
    <TabsTrigger id="table">Table</TabsTrigger>
  </TabsList>

  {/* Only the open panel is mounted — `keepMounted` opts one out. */}
  <TabsContent id="board">…</TabsContent>
  <TabsContent id="table" keepMounted>…</TabsContent>
</Tabs>

API Reference

Generated from the source. Props forwarded to the underlying element are not listed.

Tabs

Groups a list with its panels and owns which one is active.

PropTypeDefault
classNamestring | undefined—
defaultSelectedKey

The tab active on mount.

string | undefined—
onSelectionChange

Fires for a click or an arrow key — and once on mount when the root is uncontrolled and `defaultSelectedKey` names no mounted tab, reporting the tab that was…

((key: string) => void) | undefined—
orientation

The component orientation (layout flow direction).

Orientation | undefined—
render

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, TabsRootState> | undefined—
selectedKey

The active tab's `id`.

string | undefined—
style

Style applied to the element, or a function that returns a style object based on the component's state.

CSSProperties | ((state: TabsRootState) => CSSProperties | undefined) | undefined—

TabsList

The row of triggers, and the indicator that travels between them.

PropTypeDefault
activateOnFocus

Whether to automatically change the active tab on arrow key focus.

boolean | undefined—
classNamestring | undefined—
loopFocus

Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys.

boolean | undefined—
render

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, TabsListState> | undefined—
style

Style applied to the element, or a function that returns a style object based on the component's state.

CSSProperties | ((state: TabsListState) => CSSProperties | undefined) | undefined—
variant

`pill` is a control in a toolbar; `line` is page-level navigation.

TabsVariant | undefined"pill"

TabsTrigger

One trigger.

PropTypeDefault
idrequired

This tab's key, matched against the `TabsContent` carrying the same one.

string—
classNamestring | undefined—
count

Shown as a badge after the label.

number | undefined—
disabled

Whether the Tab is disabled.

boolean | undefined—
iconIconSvgElement | undefined—
nativeButton

Whether the component renders a native `<button>` element when replacing it via the `render` prop.

boolean | undefined—
render

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, TabsTabState> | undefined—
style

Style applied to the element, or a function that returns a style object based on the component's state.

CSSProperties | ((state: TabsTabState) => CSSProperties | undefined) | undefined—
tone

Colours the ICON only — the label stays on the trigger's own colour.

"info" | "success" | "warning" | "default" | "danger" | undefined—

TabsContent

The panel for the trigger of the same `id`.

PropTypeDefault
idrequired

The `id` of the trigger that opens this panel.

string—
classNamestring | undefined—
keepMounted

Whether to keep the HTML element in the DOM while the panel is hidden.

boolean | undefined—
render

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, TabsPanelState> | undefined—
style

Style applied to the element, or a function that returns a style object based on the component's state.

CSSProperties | ((state: TabsPanelState) => CSSProperties | undefined) | undefined—
Type
registry:ui
npm packages
@base-ui/react @hugeicons/react
Files written
  • components/ui/hiip-ui/tabs.tsx