Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Navigation

Workspace Switcher

Navigation

Workspace Switcher React component for Tailwind CSS v4

User MenuAlert Dialog
Live props
State
defaultValue
defaultOpen
disabled
Appearance
variant
size
logoRadius
logoVariant
meta
Behavior
search
shortcuts
responsive
shortcut
Content
searchPlaceholder
emptyText
createLabel
placeholder
sheetTitle
switchingLabel
Layout
groupBy
contentWidth288px
maxHeight460px
side
align
sideOffset8px
Code
import { WorkspaceSwitcher } from "@/components/ui/workspace-switcher"

<WorkspaceSwitcher />
  1. Home
  2. Components
  3. Navigation
  4. Workspace Switcher
Pro

Workspace Switcher React component for Tailwind CSS v4

v1.0.0·Added 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-switcher

Workspace 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.

  • value
    string

    The current workspace id (controlled). An id that is not in the list shows the placeholder.

  • defaultValuedefault ""
    string

    The 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"
    string

    The search row's placeholder.

  • emptyTextdefault "No workspaces found"
    string

    Shown when the search matches nothing.

  • groupBydefault true
    boolean

    Groups rows under their workspace.group label, in the order the groups first appear.

  • shortcutsdefault false
    boolean

    Binds 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
    () => void

    Shows the Create workspace row under the list.

  • createLabeldefault "Create workspace"
    string

    The create row's label.

  • actions
    MenuItemEntry[]

    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"
    string

    The trigger's text while no workspace is selected.

  • contentWidthdefault 288
    number

    The list's width in px; never narrower than the trigger, never wider than the screen.

  • maxHeightdefault 460
    number

    The 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.

  • sideOffsetdefault 8
    number

    Gap between the trigger and the list, in px.

  • open
    boolean

    Controlled open state; pair it with onOpenChange.

  • defaultOpendefault false
    boolean

    Starts open (uncontrolled).

  • onOpenChange
    (open: boolean) => void

    Fires when the list or the sheet opens or closes.

  • disableddefault false
    boolean

    Dims the trigger and keeps it closed.

  • responsivedefault true
    boolean

    Below 768px the list becomes a bottom sheet. Off: the popover on every screen.

  • sheetTitledefault "Switch workspace"
    string

    The phone sheet's title, and the list's accessible name.

  • shortcutdefault ""
    string

    A 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) => string

    The icon trigger's accessible name and tooltip.

  • switchingLabeldefault "Switching"
    string

    What screen readers hear for the row spinner while a switch loads.

  • container
    HTMLElement | null

    Portal target for the list and the sheet (default document.body).

  • classNames
    Partial<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.

  • className
    string

    Extra classes for the trigger, merged last. Other props (id, aria-*, data-*, handlers) are spread onto it.

Parts

  • WorkspaceLogo

    component

    The 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 | null

      The workspace to draw; null shows the empty placeholder.

    • sizedefault 32
      number

      Size in px.

    • radiusdefault "md"
      "sm" | "md" | "full"

      Corner shape.

    • variantdefault "solid"
      "soft" | "solid"

      A filled color or a soft tint behind the initials.

    • className
      string

      Extra classes.

  • workspaceInitials

    function

    The 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

    type

    One 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

@radix-ui/react-popoverlucide-reactmotionavatarbadgedrawerdropdown-menulistboxspinnertooltipuse-controllable-stateuse-media-queryuse-shortcutmotionnavigationtext-matchui-config

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.

See also

User MenuSidebarSelectListboxAvatar

Used in

Admin Shell

More React navigation components

BreadcrumbScroll ProgressProximity SidebarBounce SidebarHook SidebarGooey Nav
All 19 React navigation components
PreviousUser MenuNext Alert Dialog
NNorthwind CRM
NS

North Studio LLC

12 members · plan Pro

Overdue invoices$12,480.00#2026-0412 is the oldest
Clients128active in September
Open offers12#OF-2026-118
Pick a workspacesearch auto “Find a workspace” · empty “No workspaces found”value “north-studio” · sheet “Switch workspace” on phones