Workspace Switcher React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React workspace switcher for organizations, teams or projects: logos, plan badges, search, groups, mod+1…9 shortcuts and a create row.
Click the trigger (a card with the logo, name and plan in a sidebar header, a compact logo and name in a top bar, or the logo alone in a rail, with a tooltip) and a list of workspaces grows out of it, grouped (Personal, Teams), each row with its logo, plan badge, description and a check on the current one. Pick one and the trigger's logo and name crossfade to it; when the switch loads data (onValueChange returns a promise) the row spins and the list stays open until it finishes. Disabled workspaces are listed but cannot be picked. With more than six workspaces a search row filters by name, plan, city or keywords (diacritics do not matter) and bolds the match. Under the list sit the Create workspace row and your own actions. Keyboard: Enter, Space, Down or Up on the trigger opens it; Up / Down move, Home / End jump, Enter picks, typing filters (or jumps by first letter without search), Tab moves from the search or the list to the footer rows, leaving the footer closes, Escape closes and focus returns to the trigger. With shortcuts on, Mod+1 … Mod+9 switch to the first nine workspaces from anywhere (the hints show in the rows and the switch is announced), and a shortcut prop opens it. In a collapsed sidebar (data-collapsed="true" on a group/sidebar parent) the card shrinks to the logo. On a phone it is a bottom sheet with a title, the search under it, 48px rows and the footer rows above the safe area; drag it down or tap outside to close.
Workspace Switcher is a Pro React component in the React navigation components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add workspace-switcher, together with Avatar, Badge, Drawer, Dropdown Menu, Listbox and 9 more and the npm packages @radix-ui/react-popover, lucide-react and motion.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add workspace-switcherWorkspace Switcher is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import WorkspaceSwitcher from @/components/ui/workspace-switcher.
"use client"
import { Settings, UserPlus } from "lucide-react"
import { WorkspaceSwitcher, type Workspace } from "@/components/ui/workspace-switcher"
const workspaces: Workspace[] = [
{ id: "emma", name: "Emma Carter", logo: "/avatars/emma.jpg", plan: "Free", group: "Personal" },
{ id: "north-studio", name: "North Studio LLC", plan: "Pro", description: "12 members · New York", group: "Teams" },
{ id: "summit", name: "Summit Logistics", plan: "Business", description: "48 members · Boston", group: "Teams" },
]
export function SidebarHeader({ onSwitch }: { onSwitch: (id: string) => Promise<void> }) {
return (
<WorkspaceSwitcher
workspaces={workspaces}
defaultValue="north-studio"
onValueChange={(id) => onSwitch(id)}
shortcuts
onCreate={() => console.log("new workspace")}
createLabel="Create workspace"
actions={[
{ label: "Workspace settings", icon: <Settings />, href: "/settings" },
{ label: "Invite members", icon: <UserPlus />, onSelect: () => console.log("invite") },
]}
searchPlaceholder="Search workspaces"
emptyText="No workspaces found"
sheetTitle="Switch workspace"
/>
)
}Props
workspacesdefault"[{ id: "personal", name: "Personal", plan: "Free", description: "Just you", group: "Personal" }, { id: "acme", name: "Acme Inc", plan: "Pro", description: "12 members", group: "Teams" }, { id: "northwind", name: "Northwind", plan: "Business", description: "48 members", group: "Teams" }]"Workspace[]The workspaces: { id, name, logo?: string (image url) | ReactNode (an icon on a color from the name), description?, plan?, group?, disabled?, keywords? }. Without a logo (or when the image fails) the initials show on a color from the name, legal forms left out (North Studio LLC reads NS). The logo alone is exported as WorkspaceLogo ({ workspace, size, radius, variant }) for your own rows.
valuestringThe current workspace id (controlled). An id that is not in the list shows the placeholder.
defaultValuedefault""stringThe starting workspace id while uncontrolled. Empty: the first enabled workspace; an unknown id shows the placeholder.
onValueChange(id: string, workspace: Workspace) => (void | Promise<unknown>)Fires on a pick or a Mod+1…9 switch. Return a promise and the row shows a spinner while the list stays open; the switch commits when it resolves (a rejection keeps the current workspace).
variantdefault"card""card" | "compact" | "icon"card: logo, name, plan / description and an up-down chevron filling the width (sidebar headers; logo only in a collapsed sidebar). compact: logo, name and a chevron (top bars). icon: the logo alone with a tooltip (rails).
sizedefault"md""sm" | "md" | "lg"card and icon: 40 / 48 / 56px with a 24 / 32 / 40px logo. compact: 32 / 40 / 48px, like the buttons next to it in a top bar. 44px targets on touch.
logoRadiusdefault"md""sm" | "md" | "full"Logo corners, proportional to its size: organization logos are rounded squares, full makes circles.
logoVariantdefault"solid""solid" | "soft"The look of logos without an image: solid white initials on a color from the name, or a soft tint with deep initials.
metadefault"auto""auto" | "plan" | "description" | "none"The card's second line: the plan and the description joined with a dot (auto), one of them, or nothing.
searchdefault"auto""auto" | "always" | "never"The search row on top of the list. auto: above 6 workspaces. On touch screens it waits for a tap so the keyboard does not cover the list (unless always).
searchPlaceholderdefault"Find a workspace"stringThe search row's placeholder.
emptyTextdefault"No workspaces found"stringShown when the search matches nothing.
groupBydefaulttruebooleanGroups rows under their workspace.group label, in the order the groups first appear.
shortcutsdefaultfalsebooleanBinds Mod+1 … Mod+9 to the first nine enabled workspaces while mounted (Slack, Linear), shows the hints in the rows and announces the switch.
onCreate() => voidShows the Create workspace row under the list.
createLabeldefault"Create workspace"stringThe create row's label.
actionsMenuItemEntry[]More footer rows, in dropdown-menu's item format: { label, icon, shortcut, badge, href, onSelect, tone, disabled, keepOpen }. href rows render through NavLink; shortcuts run while it is open.
placeholderdefault"Select a workspace"stringThe trigger's text while no workspace is selected.
contentWidthdefault288numberThe list's width in px; never narrower than the trigger, never wider than the screen.
maxHeightdefault460numberThe list's maximum height in px, capped by the room on screen; the list scrolls and the footer stays.
sidedefault"bottom""top" | "right" | "bottom" | "left"Where the list opens; it flips when there is no room.
aligndefault"start""start" | "center" | "end"Alignment against the trigger.
sideOffsetdefault8numberGap between the trigger and the list, in px.
openbooleanControlled open state; pair it with onOpenChange.
defaultOpendefaultfalsebooleanStarts open (uncontrolled).
onOpenChange(open: boolean) => voidFires when the list or the sheet opens or closes.
disableddefaultfalsebooleanDims the trigger and keeps it closed.
responsivedefaulttruebooleanBelow 768px the list becomes a bottom sheet. Off: the popover on every screen.
sheetTitledefault"Switch workspace"stringThe phone sheet's title, and the list's accessible name.
shortcutdefault""stringA key combo in mod+k notation that opens and closes it from anywhere (e.g. mod+o); the trigger gets aria-keyshortcuts. Empty binds nothing.
triggerLabeldefault"(name) => "Workspace: " + name"(name: string) => stringThe icon trigger's accessible name and tooltip.
switchingLabeldefault"Switching"stringWhat screen readers hear for the row spinner while a switch loads.
containerHTMLElement | nullPortal target for the list and the sheet (default document.body).
classNamesPartial<Record<"trigger" | "logo" | "name" | "meta" | "chevron" | "content" | "search" | "list" | "option" | "optionLogo" | "optionName" | "optionDescription" | "optionPlan" | "optionShortcut" | "empty" | "footer" | "create" | "action", string>>Extra classes for the inner parts.
classNamestringExtra classes for the trigger, merged last. Other props (id, aria-*, data-*, handlers) are spread onto it.
Parts
WorkspaceLogo
componentThe organization logo on its own: the image, else the initials (or the given icon) on a color from the name. Use it in your own rows, headers or settings pages so they match the switcher.
workspace*Workspace | nullThe workspace to draw; null shows the empty placeholder.
sizedefault32numberSize in px.
radiusdefault"md""sm" | "md" | "full"Corner shape.
variantdefault"solid""soft" | "solid"A filled color or a soft tint behind the initials.
classNamestringExtra classes.
workspaceInitials
functionThe initials of an organization: the first letters of its first two words, legal forms such as LLC, Inc. or GmbH left out, so North Studio LLC reads NS. Signature: (name: string) => string.
Workspace
typeOne workspace: { id, name, logo?, description?, plan?, group?, disabled?, keywords? }.
Examples
Card, compact and icon
The sidebar header card, the top-bar compact trigger and the logo in a 64px rail with a tooltip.
14 workspaces with search
Search appears above six workspaces; matches are bold and a disabled workspace stays listed.
Mod+1 … Mod+9
shortcuts binds the first nine workspaces and shows the hints in the rows.
Create flow
onCreate opens a “New workspace” dialog and the new workspace is selected.
Footer actions
Workspace settings, Invite members and a danger row under the list; round logos, no groups.
Collapsed sidebar
The card follows the sidebar's data-collapsed attribute: the logo alone in the rail.
Sizes
sm, md and lg cards with soft logos.
Phone sheet
What the list becomes below 768px.
Dependencies
Files
- components/ui/workspace-switcher.tsx
FAQ
Is the React Workspace Switcher component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Workspace Switcher?
Run npx wingo-ui@latest add workspace-switcher. It also adds the Wingo UI items it builds on (Avatar, Badge, Drawer, Dropdown Menu, Listbox and 9 more) and the npm packages @radix-ui/react-popover, lucide-react and motion. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/workspace-switcher.
How does Workspace Switcher behave on phones?
Below 768px the list becomes a bottom sheet. Off: the popover on every screen. The responsive prop controls this and is on by default.