Source: https://monarchui.dev/components/workspace-switcher/

Components · Identity

# WorkspaceSwitcher

Switch between the workspaces a person belongs to, with search and Create workspace.

@rookcom/monarch-react/shell

Component

## Switch, create and manage

## Usage examples

### Collapsed, and nothing chosen yet

## Purpose

Every Rook app lets a person move between their workspaces from the rail. One shared control names its trigger, marks the current workspace for screen readers and supports search.

### Use when

- The rail’s `workspaceSwitcher` slot, or the top of a settings sidebar.

### Avoid when

- Choosing a Space inside the workspace: use SpaceSwitcher.
- Picking a workspace as a form value (inviting to a workspace): use EntityPicker in a Field.

## Design principles

- The app does the switch: `onValueChange` gets the id and the app navigates (and may keep the sub-page).
- Square avatars for workspaces (round ones are people), with initials when there is no logo.
- The current workspace is `aria-selected`, never marked by a coloured dot alone.
- Search matches names, handles and hidden keywords such as old names.

## API reference

```tsx
import { WorkspaceSwitcher } from '@rookcom/monarch-react/shell';
```

| Export | Kind | Rendering | Root class |
| --- | --- | --- | --- |
| `WorkspaceSwitcher` | component | client | `.mn-workspace-switcher` |

Forwards its ref to the trigger button. Built on EntityPicker and Avatar from `@rookcom/monarch-react`.

`WorkspaceOption` is `{ id, name, handle?, avatarUrl?, keywords? }`.

Generated from the TypeScript declarations of `packages/react/src/components/identity/workspace-switcher`.

## Properties

Controlled with `value` + `onValueChange`, or uncontrolled with `defaultValue`. There is no "none" choice.

`onCreate` or `createHref` adds "Create workspace"; `manageHref` adds "All workspaces" (new tab unless `manageExternal={false}`).

### `WorkspaceSwitcher`

Switches between the workspaces a person belongs to: an avatar trigger (name and handle when there is room), a searchable popover with the current workspace checked, and Create / All workspaces actions. Keyboard: the listbox model of EntityPicker (arrows, Home/End, type to search, Enter, Escape returns focus). The app supplies the workspaces and does the switch.

| Prop | Type | Description |
| --- | --- | --- |
| `workspaces` required | `WorkspaceOption[]` | The workspaces this person belongs to. |
| `align` | `"center" \| "start" \| "end"` |  |
| `className` | `string` |  |
| `collapsed` | `boolean` | Avatar-only trigger for a collapsed rail; the name moves into a tooltip. |
| `createHref` | `string` | …or a link. |
| `defaultValue` | `string \| null` |  |
| `loading` | `boolean` |  |
| `manageExternal` | `boolean` | Open `manageHref` in a new tab. Default true. |
| `manageHref` | `string` | "All workspaces" link (usually Rook ID). |
| `messages` | `Partial<WorkspaceSwitcherMessages>` |  |
| `onCreate` | `(() => void)` | "Create workspace": an action (the popover closes first)… |
| `onOpenChange` | `((open: boolean) => void)` |  |
| `onValueChange` | `((workspaceId: string) => void)` | Called with the chosen workspace id. The app navigates (and keeps the sub-page if it wants). |
| `open` | `boolean` |  |
| `value` | `string \| null` | The current workspace id (controlled). |

## Variants

| Prop | Values | Description |
| --- | --- | --- |
| `collapsed` | `'false'``'true'` | Avatar-only trigger with the name in a tooltip, for the 48px rail. |

## States

| State | Exposed as | Behaviour |
| --- | --- | --- |
| Current workspace | `aria-selected="true"` | Checked in the list; the trigger shows its name and @handle. |
| Nothing chosen | – | The trigger reads "Workspace: Choose a workspace" with a neutral icon. |
| Loading | – | The list says "Loading workspaces…". |
| Open | `aria-expanded="true"` | On the trigger. |

## Interaction behaviour

- Opening moves focus to the search box; choosing closes the popover and returns focus to the trigger.
- Choosing the current workspace again does nothing.
- "Create workspace" closes the popover first, then runs `onCreate` (open your dialog there).

## Keyboard support

| Keys | Action |
| --- | --- |
| Enter / Space / ↓ | Open from the trigger. |
| ↓ / ↑ / Home / End | Move through the workspaces. |
| Typing | Filter by name, handle or keyword. |
| Enter | Switch to the highlighted workspace. |
| Escape | Close and return focus to the trigger. |

## Accessibility

Role

button opening a dialog with a search combobox and a listbox

Accessible name

The trigger is named by its text, "Workspace: <name> @handle" (

messages.label

is visually hidden), also when collapsed to an avatar.

- Fixes the audited gaps: a collapsed trigger without a name and an active item shown by colour only.
- Avatars are decorative; the option label and @handle name each workspace.
- The All workspaces link announces the new tab.

## Responsive behaviour

- The expanded trigger fills its container and truncates long names; the popover never exceeds the screen width.

## Theme support

- Tokens only; inside RookRail the trigger uses the accent panel colours.
- Transitions are removed under reduced motion.

## Do and don't

### Do

- Keep the sub-page when switching if the page exists in the other workspace.
- Pass workspace logos as `avatarUrl`; initials are the fallback.

### Don't

- Do not list workspaces the person cannot open.
- Do not put Space choices in this list.

## Related components

- [SpaceSwitcher](https://monarchui.dev/components/space-switcher/): The Space inside the workspace.
- [EntityPicker](https://monarchui.dev/components/entity-picker/): The picker it is built on.
- [Avatar](https://monarchui.dev/components/avatar/): Workspace logos and initials.
- [RookRail](https://monarchui.dev/components/rook-rail/): Has a slot for it.

## Dependencies

| Kind | Depends on |
| --- | --- |
| npm packages | `react` |
| Monarch components | [Avatar](https://monarchui.dev/components/avatar/), [EntityPicker](https://monarchui.dev/components/entity-picker/) |

## Testing coverage

- Trigger named with the current workspace and its handle.
- List with the current one selected; switching calls `onValueChange` and returns focus.
- Keyboard search over names, handles and keywords; re-choosing the current one is ignored; controlled mode.
- Create workspace (action and link) and All workspaces; placeholder and empty message; collapsed tooltip.

| Check | Covered |
| --- | --- |
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery/workspace-switcher; Cross-browser axe: /components/workspace-switcher |

Tests: `packages/react/src/components/identity/workspace-switcher`

## All example code

### Switch, create and manage

```tsx
import { useState } from 'react';
import { WorkspaceSwitcher } from '@rookcom/monarch-react/shell';

const workspaces = [
  { id: 'w1', name: 'Acme', handle: 'acme' },
  { id: 'w2', name: 'Northfield Traders', handle: 'northwind' },
  { id: 'w3', name: 'Globex', handle: 'globex', keywords: 'globex corporation' },
];

function Basic() {
  const [workspace, setWorkspace] = useState('w1');
  return (
    <div style={{ width: '15rem' }}>
      <WorkspaceSwitcher
        workspaces={workspaces}
        value={workspace}
        onValueChange={setWorkspace}
        onCreate={() => {}}
        manageHref="https://id.example.com/workspaces"
      />
    </div>
  );
}

<Basic />
```

### Collapsed, and nothing chosen yet

```tsx
import { WorkspaceSwitcher } from '@rookcom/monarch-react/shell';

const workspaces = [
  { id: 'w1', name: 'Acme', handle: 'acme' },
  { id: 'w2', name: 'Northfield Traders', handle: 'northwind' },
  { id: 'w3', name: 'Globex', handle: 'globex', keywords: 'globex corporation' },
];

function Compact() {
  return (
    <div style={{ display: 'flex', gap: 'var(--mn-space-3)', alignItems: 'center' }}>
      <WorkspaceSwitcher workspaces={workspaces} defaultValue="w2" collapsed />
      <div style={{ width: '15rem' }}>
        <WorkspaceSwitcher workspaces={workspaces} createHref="#new-workspace" />
      </div>
    </div>
  );
}

<Compact />
```
