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.
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 NavigationPreview
Switch between light and dark to inspect the embedded Storybook preview.
Installation
pnpm dlx shadcn@latest add https://ui.vllnt.com/r/workspace-switcher.jsonStorybook
Explore all variants, controls, and accessibility checks in the interactive Storybook playground.
View in StorybookCode
"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 };
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.