Skip to content

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.

ShapeRegistryWhere it shows up
App<Desktop apps={...} />Dock tile, menu bar focus name, Spotlight, Cmd/Ctrl + 1..9
SystemWindowDefregisterSystemWindow(id, def)Spotlight, desktop icon (when appearsAsDesktopIcon passes), windowable
SpotlightSourceregisterSpotlightSource(id, fn)Cmd-K palette only: for anything that activates instead of opening
StatusItemregisterStatusItem({ id, ... })Menu-bar right cluster between workspaces and the clock
QuickSettingItemregisterQuickSetting({ 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.

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:

  1. A module-level store owns the state.
  2. A component mounted by <Desktop> subscribes via useSyncExternalStore.
  3. An imperative function (notify, showHud, openContextMenu, etc.) writes to the store.
SystemImperative callMounted by <Desktop>
Notificationsnotify({ title, ... })<NotificationToasts> + <NotificationCenter>
Context menuopenContextMenu({ x, y, ... })<ContextMenu>
Snap previewsetSnapPreview({ ... })<SnapPreview>
HUDshowHud({ title, ... })<HudOverlay>
Quick settingsregisterQuickSetting({ ... })<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 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.

  • @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 on core for types and hooks, never on a specific theme. Every visual decision comes from useTheme().
  • @react-ui-os/theme-*: pure data. Depends on core for types only.

A theme that can’t express a token a component needs means the contract is undersized, not the theme.

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