Workspace Switcher

Let users switch between workspaces or contexts with a segmented, radio-group pill control in React. Controlled or uncontrolled. Install via the shadcn CLI.

Report a bug

What it is & when to use

Workspace Switcher is a compact, segmented control for moving between workspaces, canvas views, or operational contexts. It renders each option as a pill inside a radio group, highlighting the active one, and works either controlled or uncontrolled through value and defaultValue props. An onValueChange callback reports selection, and each option can carry a description shown as a tooltip and, on wider screens, inline. Reach for it when users toggle between a handful of top-level contexts rather than navigating a deep menu.

Browse all Navigation

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.com/r/workspace-switcher.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

Code

"use client"; import { useMemo, useState } from "react"; import { cn } from "../../lib/utils"; export type WorkspaceOption = { description?: string; id: string; label: string; }; export type WorkspaceSwitcherProps = Omit< React.ComponentPropsWithoutRef<"div">, "defaultValue" | "onChange" > & { defaultValue?: string; onValueChange?: (value: string) => void; value?: string; workspaces: WorkspaceOption[]; }; const WorkspaceSwitcher = ({ className, defaultValue, onValueChange, ref, value, workspaces, ...props }: WorkspaceSwitcherProps & { ref?: React.Ref<HTMLDivElement> }) => { const fallbackValue = defaultValue ?? workspaces[0]?.id ?? ""; const [internalValue, setInternalValue] = useState(fallbackValue); const currentValue = value ?? internalValue; const currentWorkspace = useMemo( () => workspaces.find((workspace) => workspace.id === currentValue), [currentValue, workspaces], ); function handleSelect(nextValue: string) { if (value === undefined) { setInternalValue(nextValue); } onValueChange?.(nextValue); } return ( <div className={cn( "inline-flex min-w-0 items-center gap-1 rounded-full border border-border/70 bg-muted/50 p-1", className, )} ref={ref} role="radiogroup" {...props} > {workspaces.map((workspace) => { const isActive = workspace.id === currentValue; return ( <button aria-checked={isActive} className={cn( "rounded-full px-3 py-1.5 text-sm font-medium transition-colors", isActive ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground", )} key={workspace.id} onClick={() => { handleSelect(workspace.id); }} role="radio" title={workspace.description} type="button" > {workspace.label} </button> ); })} {currentWorkspace?.description ? ( <span className="hidden pl-2 pr-1 text-xs text-muted-foreground md:inline"> {currentWorkspace.description} </span> ) : null} </div> ); }; WorkspaceSwitcher.displayName = "WorkspaceSwitcher"; export { WorkspaceSwitcher };

Dependencies

  • @vllnt/ui@^0.3.0

Frequently asked questions

What is Workspace Switcher?
Workspace Switcher is a React component that renders a segmented pill control for switching between workspaces, canvas views, or operational contexts. Options are supplied as an array of id and label pairs, and the active one is visually highlighted.
How do I add Workspace Switcher?
Run `pnpm dlx shadcn@latest add https://ui.vllnt.com/r/workspace-switcher.json` to install it into your React project via the shadcn CLI, then pass your workspaces array to the WorkspaceSwitcher component.
Is Workspace Switcher controlled or uncontrolled?
It works both ways. Pass value with onValueChange for a controlled component, or defaultValue to let it manage its own selection. It renders as an accessible radio group, marking the selected workspace with aria-checked.