Installation
Requirements
Section titled “Requirements”- React 19+
- A bundler that respects
package.jsonexports. Vite, Astro, Next.js (App Router), Remix, and TanStack Start all qualify. Webpack 4 does not. Use Webpack 5+. - TypeScript is optional but recommended. The types are bundled.
Install
Section titled “Install”pnpm add @react-ui-os/core @react-ui-os/desktop @react-ui-os/theme-macosnpm install @react-ui-os/core @react-ui-os/desktop @react-ui-os/theme-macosbun add @react-ui-os/core @react-ui-os/desktop @react-ui-os/theme-macosOptional: pick a branded theme.
pnpm add @react-ui-os/theme-windowsnpm install @react-ui-os/theme-windowsYour first desktop
Section titled “Your first desktop”import type { App } from "@react-ui-os/core";import { Desktop } from "@react-ui-os/desktop";import { macosTheme } from "@react-ui-os/theme-macos";
const apps: App[] = [ { id: "notes", name: "Notes", accent: "#f59e0b", content: () => ( <article> <h1>Hello, desktop.</h1> <p>Drag this window by the title bar. Try Cmd-K.</p> </article> ), },];
export default function App() { return <Desktop apps={apps} theme={macosTheme} />;}That single component renders the full desktop. No other styles to import; the library injects its own keyframes at the root.
The library mounts client-side. The bundles ship their "use client" directives, so importing the components in Next.js App Router needs no wrapper module. Your own modules still need the directive when they define app content components, because component functions cannot cross the server-to-client boundary as props:
"use client";
import { Desktop } from "@react-ui-os/desktop";import { apps } from "./apps"; // defines content components, so this module is client code// ...In Astro, mount the desktop as a React island with client:load:
---import Desktop from "../components/Desktop";---<Desktop client:load />Server-side, the desktop renders nothing (every JSX surface that touches window returns null), so hydration is safe.
Troubleshooting
Section titled “Troubleshooting”The dock appears but windows don’t drag.
Confirm React 19 and that your bundler resolves the package.json "exports" field (Vite, Next.js, and Webpack 5+ do; Webpack 4 does not).
Styles leak from a parent layout.
The library uses position: fixed; inset: 0 on the desktop root. Make sure no parent forces overflow: hidden on the entire viewport before the desktop mounts.
Custom event collisions.
The library dispatches events on the namespace react-ui-os:*. If your app uses the same prefix, override the storage adapter prefix:
import { createLocalStorageAdapter } from "@react-ui-os/core";
const storage = createLocalStorageAdapter("acme-os");<Desktop apps={apps} theme={theme} storage={storage} />;