Components · Identity
SpaceSwitcher
Pick "All spaces" or one Space of the workspace, with search, New and Manage links.
All spaces, with New and Manage
import { useState } from 'react';
import { SpaceSwitcher } from '@rookcom/monarch-react/shell';
const spaces = [
{ id: 's-eng', name: 'Engineering', description: '18 members' },
{ id: 's-mkt', name: 'Marketing', description: '9 members' },
{ id: 's-ops', name: 'Operations', description: '6 members' },
{ id: 's-sales', name: 'Sales', description: '12 members' },
];
function Basic() {
const [space, setSpace] = useState<string | null>(null);
return (
<div style={{ width: '15rem' }}>
<SpaceSwitcher
spaces={spaces}
value={space}
onValueChange={setSpace}
manageHref="https://id.example.com/spaces"
onCreate={() => {}}
/>
</div>
);
}
<Basic />Usage examples
Collapsed, and without All spaces
import { SpaceSwitcher } from '@rookcom/monarch-react/shell';
const spaces = [
{ id: 's-eng', name: 'Engineering', description: '18 members' },
{ id: 's-mkt', name: 'Marketing', description: '9 members' },
{ id: 's-ops', name: 'Operations', description: '6 members' },
{ id: 's-sales', name: 'Sales', description: '12 members' },
];
function Compact() {
return (
<div style={{ display: 'flex', gap: 'var(--mn-space-3)', alignItems: 'center' }}>
<SpaceSwitcher spaces={spaces} defaultValue="s-mkt" collapsed />
<div style={{ width: '13rem' }}>
<SpaceSwitcher spaces={spaces} defaultValue="s-ops" allowAll={false} />
</div>
</div>
);
}
<Compact />Purpose
Rook workspaces are split into Spaces (department-like slices managed in Rook ID). The switcher sets which Space the app shows. Its listbox supports arrow-key navigation.
Use when
- An app that scopes its content by Space, in the rail’s
spaceSwitcherslot or a page header.
Avoid when
- Switching workspaces: use WorkspaceSwitcher.
- Filtering a single list by team or department: use a filter in FilterBar.
Design principles
- Data in, choice out: the app passes the Spaces this person may open and stores the choice; the switcher never fetches or persists.
- One listbox model (EntityPicker): arrow keys, Home/End, type to search, Enter to choose, Escape to close; options are real
options, not buttons. - "All spaces" is a first-class choice, shown first and checked when nothing is chosen.
- A Space that disappears (deleted, access removed) reads as "All spaces" instead of a stale name.
API reference
import { SpaceSwitcher } from '@rookcom/monarch-react/shell';| Export | Kind | Rendering | Root class |
|---|---|---|---|
SpaceSwitcher | component | client | .mn-space-switcher |
Forwards its ref to the trigger button. Built on EntityPicker from @rookcom/monarch-react.
SpaceOption is { id, name, description?, icon? }.
Generated from the TypeScript declarations of packages/react/src/components/identity/space-switcher.
Properties
value is a Space id or null for "All spaces"; controlled with value + onValueChange, or uncontrolled with defaultValue.
manageHref opens in a new tab unless manageExternal={false}; onCreate or createHref adds "New Space".
SpaceSwitcher
Chooses the Space (a department-like slice of a workspace) the app shows: "All spaces" or one Space. A popover with search, the current Space checked, and New / Manage links; the list is a listbox with arrow keys, Home/End and type-to-search (EntityPicker). The product supplies the Spaces and stores the choice; the switcher never fetches.
| Prop | Type | Description |
|---|---|---|
spaces required | SpaceOption[] | The Spaces of the current workspace that this person can open. |
align | "center" | "start" | "end" | Popover alignment. Default `start`. |
allowAll | boolean | Offer "All spaces" first. Default true. Without it a Space must stay chosen. |
className | string | |
collapsed | boolean | Icon-only trigger for a collapsed rail; the name moves into a tooltip. |
createHref | string | "New Space": a link… |
defaultValue | string | null | |
hideWhenEmpty | boolean | Render nothing when there are no Spaces and nothing is loading. Default true. |
loading | boolean | Spaces are still loading: the list says so. |
manageExternal | boolean | Open `manageHref` in a new tab. Default true. |
manageHref | string | "Manage Spaces" link (usually Rook ID). |
messages | Partial<SpaceSwitcherMessages> | Interface text, for translation. |
onCreate | (() => void) | …or an action (the popover closes first). |
onOpenChange | ((open: boolean) => void) | |
onValueChange | ((spaceId: string | null) => void) | |
open | boolean | Open (controlled). |
value | string | null | The chosen Space id; `null` is "All spaces" (controlled). |
Variants
| Prop | Values | Description |
|---|---|---|
collapsed | 'false''true' | Icon-only trigger with the current Space in a tooltip, for the 48px rail. |
allowAll | 'true''false' | Offer "All spaces". Without it a Space must stay chosen. |
States
| State | Exposed as | Behaviour |
|---|---|---|
| All spaces | data-all | Nothing chosen: the trigger reads "Space: All spaces". |
| Current Space | aria-selected="true" | Checked in the list and named on the trigger. |
| Loading | – | The list says "Loading spaces…" while loading. |
| Empty | – | With no Spaces and nothing loading the switcher renders nothing (hideWhenEmpty), as the products did. |
| Open | aria-expanded="true" | On the trigger. |
Interaction behaviour
- Clicking the trigger (or Enter, Space, ↓) opens the popover with focus in the search box.
- Typing filters by name; choosing closes the popover and returns focus to the trigger.
- "New Space" closes the popover, then runs
onCreate(or followscreateHref); "Manage Spaces" opens Rook ID in a new tab.
Keyboard support
| Keys | Action |
|---|---|
| Enter / Space / ↓ | Open the popover from the trigger. |
| ↓ / ↑ | Move through the Spaces. |
| Home / End | First / last Space. |
| Enter | Choose the highlighted Space. |
| Tab | From the search box to New / Manage. |
| Escape | Close and return focus to the trigger. |
Accessibility
- Role
- button opening a dialog that contains a combobox (search) and a listbox
- Accessible name
- The trigger is "Space: <current>" (
messages.label); the popover is named "Space".
- Follows the APG combobox pattern with a listbox popup (EntityPicker); the active option is tracked with
aria-activedescendant. - The collapsed trigger keeps its full name and adds a tooltip.
- The Manage link announces that it opens in a new tab.
Responsive behaviour
- The trigger fills its container and truncates long names.
- The popover is 18rem wide, never wider than the screen minus 2rem; New / Manage rows are 44px tall on touch screens.
Theme support
- Surface, border and text tokens; inside RookRail the trigger switches to the accent panel colours.
- Transitions are removed under reduced motion.
Do and don't
Do
- Pass only the Spaces this person may open, and still check access on the server.
- Remember the choice per workspace in the app (Rook apps share it across apps).
- Use the rail slot function to pass
collapsed.
Don't
- Do not fetch Spaces inside a slot render on every render; load them once and pass the list.
- Do not use it for navigation between pages.
Related components
- WorkspaceSwitcher: The workspace above the Space.
- EntityPicker: The picker it is built on.
- RookRail: Has a slot for it.
Dependencies
| Kind | Depends on |
|---|---|
| npm packages | react |
| Monarch components | EntityPicker |
Testing coverage
- Trigger named with the current Space; starts on All spaces.
- Popover with search focused, the current Space selected and the Manage link.
- Typing filters; arrow keys and Enter choose; focus returns to the trigger.
- All spaces clears the choice;
allowAll={false}hides it; controlled mode; a missing Space reads as All spaces. - New Space runs after closing; Escape returns focus; hidden when empty, loading message; collapsed tooltip; translation.
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery/space-switcher; Cross-browser axe: /components/space-switcher |
Tests: packages/react/src/components/identity/space-switcher