Workspace Switcher
Find and switch between teams, organizations or projects, or create a new one.
import { Workspace, WorkspaceSwitcher } from '@youj/react/workspace-switcher'
Installation & usage
npm i @youj/react cmdkFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Requires cmdk, an optional dependency. Import it from @youj/react/workspace-switcher.
Use it at the top of a Sidebar, or at the start of an App Header, when people belong to several workspaces and move between them often. The trigger always says which one they're in. The list is searchable, so it holds up at 3 workspaces or 300. For a short list that sits among other account items, the workspaces option of the Account Menu is enough.
import { WorkspaceSwitcher, type Workspace } from '@youj/react/workspace-switcher'
const workspaces: Workspace[] = [
{ id: 'northwind', name: 'Northwind Labs', detail: 'Pro · 12 members' },
{ id: 'acme', name: 'Acme Corp', detail: 'Enterprise · 148 members' },
]
export function Switcher() {
const [workspace, setWorkspace] = useState('northwind')
return <WorkspaceSwitcher workspaces={workspaces} value={workspace} onValueChange={setWorkspace} onCreate={createWorkspace} />
}Each workspace has an id, a name, an optional logo (its initials show until the image loads, or if it fails) and an optional detail line such as the plan or member count. The trigger shows the current one, and the list marks it with a check. It's controlled with value and onValueChange, or uncontrolled with defaultValue.
Creating a workspace
Pass onCreate and a "Create workspace" row sits under the list. It carries what was typed in the search, so someone who looks for a workspace that isn't there can press Enter to create it with that name: with no match left, the create row is the active one. onCreate receives the trimmed search text, which is empty when nothing was typed. Rename the row with createLabel. Search is plain substring matching, so a new name never fuzzily matches an existing one.
Description
A switcher in a small sidebar card; searching for a name that doesn't exist offers to create it.
A logo-only switcher at the top of an icon rail, opening its list beside the rail.
compact shows only the logo, for a sidebar collapsed to icons. The name moves into the trigger's accessible name, and side opens the list beside the rail instead of below it.
Code
workspace-switcher-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propsworkspaces, value, defaultValue… (11)
Props
| Prop | Type | |
|---|---|---|
workspacesRequired | Workspace[] | – |
value | string | – |
defaultValue | string | – |
onValueChange | (id: string) => void | – |
onCreate | (name: string) => voidAdds the "Create workspace" row. Receives what was typed in the search. | – |
createLabel | string | – |
searchPlaceholder | string | – |
compact | booleanOnly the logo: for a sidebar collapsed to icons. default false | false |
align | Align'end''start''center'default 'start' | 'start' |
side | Side'left''right''top''bottom''inline-end''inline-start' | – |
className | stringThe trigger's class. | – |
MotionOpen, close, chevrons… · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
Openscale .96 → 1 and fade, from the trigger (yj-pop) | duration.fastease.enter140ms · cubic-bezier(0.16, 1, 0.3, 1) | 140mscubic-bezier(0.16, 1, 0.3, 1) | |
| Close | duration.instantease.exit80ms · cubic-bezier(0.4, 0, 1, 1) | 80mscubic-bezier(0.4, 0, 1, 1) | |
Chevronspart while the list is open (ChevronsUpDownIcon) | no token per icon | per icon | |
| Highlightthe active row’s background glides with ↑ and ↓ | spring.smooth0.36s, bounce 0 | 0.36s, bounce 0 | |
| Checkdraws in on the current workspace | duration.slowease.emphasized340ms · cubic-bezier(0.6, 0, 0.1, 1) | 340mscubic-bezier(0.6, 0, 0.1, 1) | |
| Trigger hoverbackground | duration.fastease.standard140ms · cubic-bezier(0.2, 0, 0, 1) | 140mscubic-bezier(0.2, 0, 0, 1) |
Reduced motion
The list opens and closes at once, the highlight jumps between rows, and the check appears already drawn.
Reduced motion
The popover appears and disappears at once, the highlight moves between rows without gliding, and the chevrons and check change without animating. Search, focus and keyboard handling work the same.
AccessibilityKeyboard, focus, announcements
Accessibility
- The trigger is a button named after the current workspace ("Northwind Labs, switch workspace"), which also covers the compact trigger. The logos are decorative.
- It opens with Enter, Space or ↓, and focus lands in the search field. ↑ and ↓ move the active row, Enter chooses it, and Escape closes the list and returns focus to the trigger.
- The list is the Command listbox, so the active row is announced as you move, and the current workspace as selected. Each row's detail is read after its name.
- The search clears once the list has closed, so it opens fresh next time.