Patterns
The library is bigger than it looks because most “new features” follow one of a handful of recurring shapes. Read this page once and you will recognize half the source.
Contribution shapes: what consumers register
Section titled “Contribution shapes: what consumers register”The library exposes five points at which a consumer hands the system something to render or surface.
| Shape | Registry | Where it shows up |
|---|---|---|
App | <Desktop apps={...} /> | Dock tile, menu bar focus name, Spotlight, Cmd/Ctrl + 1..9 |
SystemWindowDef | registerSystemWindow(id, def) | Spotlight, desktop icon (when appearsAsDesktopIcon passes), windowable |
SpotlightSource | registerSpotlightSource(id, fn) | Cmd-K palette only: for anything that activates instead of opening |
StatusItem | registerStatusItem({ id, ... }) | Menu-bar right cluster between workspaces and the clock |
QuickSettingItem | registerQuickSetting({ id, ... }) | The Quick Settings popover (when chrome.quickSettings is set) |
All five are declarative. You hand the library a description; it owns the rendering, the keyboard nav, the focus, the dismissal. Re-registering the same id replaces the previous record so a host component can update without churn.
The imperative-store pattern
Section titled “The imperative-store pattern”Several systems need to be triggerable from any code (a fetch handler, an effect, a service worker, an event listener), not just from within a React subtree. The library solves this the same way every time:
- A module-level store owns the state.
- A component mounted by
<Desktop>subscribes viauseSyncExternalStore. - An imperative function (
notify,showHud,openContextMenu, etc.) writes to the store.
| System | Imperative call | Mounted by <Desktop> |
|---|---|---|
| Notifications | notify({ title, ... }) | <NotificationToasts> + <NotificationCenter> |
| Context menu | openContextMenu({ x, y, ... }) | <ContextMenu> |
| Snap preview | setSnapPreview({ ... }) | <SnapPreview> |
| HUD | showHud({ title, ... }) | <HudOverlay> |
| Quick settings | registerQuickSetting({ ... }) | <QuickSettings> |
This is how a deep fetch handler can notify({ title: "Saved" }) without anyone threading a dispatcher down. Stores live in @react-ui-os/core so they have no React dependency; the components that paint them live in @react-ui-os/desktop.
When you add a system-wide feature, reach for this pattern before reaching for a new context provider. Reserve providers for state that genuinely belongs to a subtree (the Window’s drag state, for example).
The three depths
Section titled “The three depths”The same primitives are reachable at three levels. The easy thing is short; the hard thing is reachable; nothing forces you to step down a level if you don’t need to.
// 1. One-line desktop. Default composition.<Desktop apps={apps} theme={theme} />
// 2. Composable provider. Pick which surfaces to render.<DesktopProvider apps={apps} theme={theme}> <Wallpaper /> <MenuBar /> <CustomGreeting /> {/* your own surface */} <WindowLayer /> <Dock /> <NotificationToasts /> <ContextMenu /></DesktopProvider>
// 3. Hooks. Drive the system from outside the default chrome.const { openWindow, focusedWindow, switchWorkspace } = useWindowManager();const theme = useTheme();const apps = useApps();const { unreadCount } = useNotifications();If you reach for depth 3 features on the most common path, the API is undersized at depth 1 and the library should grow.
What goes in core vs desktop vs themes
Section titled “What goes in core vs desktop vs themes”@react-ui-os/core: types, hooks, the window-manager reducer, the storage adapter, the settings applier, vanilla stores (notifications). No JSX outside the provider. No browser globals at module load. No theme assumptions.@react-ui-os/desktop: the components. Depends oncorefor types and hooks, never on a specific theme. Every visual decision comes fromuseTheme().@react-ui-os/theme-*: pure data. Depends oncorefor types only.
A theme that can’t express a token a component needs means the contract is undersized, not the theme.
Mounted-by-<Desktop> vs. opt-in
Section titled “Mounted-by-<Desktop> vs. opt-in”<Desktop> mounts the system surfaces by default: the notification stack, the Center, the context menu renderer, the snap preview, the HUD, the app switcher, Mission Control, the desktop backdrop, the Spotlight, the keyboard shortcuts, the wallpaper, the menu bar, the dock, the window layer.
To opt out of any one, drop down to <DesktopProvider> and mount only what you want. That’s the only way to remove default behavior. The library does not gate it behind feature flags.
See also
Section titled “See also”useWindowManager: the central hook.registerSpotlightSource: a contribution shape in detail.- Notifications: the imperative-store pattern’s canonical example.
- Recipes: concrete patterns built on top.