Skip to content

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 cmdk

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

Reference

Propsworkspaces, value, defaultValue… (11)

Props

WorkspaceSwitcher props
PropType
workspacesRequiredWorkspace[]–
valuestring–
defaultValuestring–
onValueChange(id: string) => void–
onCreate(name: string) => void

Adds the "Create workspace" row. Receives what was typed in the search.

–
createLabelstring–
searchPlaceholderstring–
compactboolean

Only the logo: for a sidebar collapsed to icons.

default false

false
alignAlign
'end''start''center'

default 'start'

'start'
sideSide
'left''right''top''bottom''inline-end''inline-start'
–
classNamestring

The trigger's class.

–
MotionOpen, close, chevrons… · reduced motion

Motion

Motion tokens
MomentTokens
Openscale .96 → 1 and fade, from the trigger (yj-pop)
duration.fastease.enter

140ms · cubic-bezier(0.16, 1, 0.3, 1)

140mscubic-bezier(0.16, 1, 0.3, 1)
Close
duration.instantease.exit

80ms · 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.smooth

0.36s, bounce 0

0.36s, bounce 0
Checkdraws in on the current workspace
duration.slowease.emphasized

340ms · cubic-bezier(0.6, 0, 0.1, 1)

340mscubic-bezier(0.6, 0, 0.1, 1)
Trigger hoverbackground
duration.fastease.standard

140ms · 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.