Expandable Tabs
ericts-ui/expandable-tabsFreeComponent
A DynamicIsland-style toolbar whose icon tabs expand into actionable menu or content panels, with full keyboard and ARIA support.
Live preview
live · rendered by the registry
Rendered by ericts-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.ericts.com/r/expandable-tabs.jsonSource
1"use client";23import {4 AnimatePresence,5 motion,6 useReducedMotion,7 type Variants,8} from "motion/react";9import {10 useCallback,11 useEffect,12 useId,13 useMemo,14 useRef,15 useState,16 type KeyboardEvent,17 type ReactNode,18} from "react";1920import {21 useElementSizeMap,22 type ElementSize,23} from "@/hooks/use-element-size-map";24import { cn } from "@/lib/utils";2526// Inlined so the registry item is self-contained. Strong custom ease — the27// defaults like `ease-out` feel weak for a morphing surface like this.28const EASE_OUT = [0.16, 1, 0.3, 1] as const;2930/** A single actionable row inside a tab's menu panel. */31export type ExpandableTabMenuItem = {32 id: string;33 label: string;34 /** Optional secondary line shown under the label. */35 description?: string;36 icon?: ReactNode;37 /** Hint shown on the trailing edge (e.g. a keyboard shortcut). */38 shortcut?: string;39 disabled?: boolean;40 /** Fired when this row is chosen by click, Enter, or Space. */41 onSelect?: () => void;42};4344type ExpandableTabBase = {45 id: string;46 /** Shown inside the active tab and used as the trigger's accessible name. */47 label: string;48 icon: ReactNode;49 disabled?: boolean;50};5152/**53 * A tab is one of three shapes:54 * - `items` — opens a panel of selectable menu rows.55 * - `content` — opens a panel of arbitrary content (form, search, …).56 * - `onSelect` — no panel; fires immediately like a toolbar button.57 */58export type ExpandableTabItem = ExpandableTabBase &59 (60 | { items: ExpandableTabMenuItem[]; content?: never; onSelect?: never }61 | { content: ReactNode; items?: never; onSelect?: never }62 | { onSelect: () => void; items?: never; content?: never }63 );6465export type ExpandableTabsClassNames = {66 root?: string;67 panel?: string;68 bar?: string;69 tab?: string;70 activeTab?: string;71 icon?: string;72 label?: string;73 pill?: string;74 menu?: string;75 menuItem?: string;76};7778export interface ExpandableTabsProps {79 items: ExpandableTabItem[];80 /** Open tab id, or null/undefined for the closed (bar-only) state. */81 value?: string | null;82 defaultValue?: string | null;83 onValueChange?: (id: string | null) => void;84 /** Fired when a menu row is chosen: (tabId, itemId). */85 onSelect?: (tabId: string, itemId: string) => void;86 /** Collapse the panel after a menu row is chosen. Default true. */87 closeOnSelect?: boolean;88 /** Accessible name for the toolbar. */89 "aria-label"?: string;90 className?: string;91// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from ericts-ui
All 38 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Adaptive Draweradaptive-drawer | ericts-ui | Components | Free | 2 deps | |
| Check Animationcheck-animation | ericts-ui | Components | Free | no deps · 2 files | |
| Context Cursorcontext-cursor | ericts-ui | Components | Free | 1 dep | |
| Copy Buttoncopy-button | ericts-ui | Components | Free | 2 deps | |
| Expandable Dialogexpandable-modal | ericts-ui | Components | Free | 1 dep | |
| Expandable Toolbarexpandable-toolbar | ericts-ui | Components | Free | 1 dep | |
| Expanding Panelexpanding-panel | ericts-ui | Components | Free | 2 deps | |
| Expanding Segmented Tabsexpanding-segmented-tabs | ericts-ui | Components | Free | 1 dep |
