Launcher
The launcher is the keyboard front door: type a few letters, get the right window. Every OS presents this differently, so the library splits it into a shared brain and a swappable face. The data (apps, system windows, and registerSpotlightSource rows), the Cmd-K shortcut, selection, and activation are the same in every register; theme.chrome.launcher picks the layout.
Presentations
Section titled “Presentations”theme.chrome.launcher chooses the face. All three carry the same results and filter the same way.
| Value | Reads as | Layout |
|---|---|---|
"spotlight" (default) | macOS Spotlight | A centered command palette: search field over a vertical result list. |
"grid" | GNOME Activities | A full-bleed overview: a centered search field over a grid of large app icons. |
"menu" | Windows Start | A panel raised from the dock launcher: a search field over a grid of app tiles. |
The default theme uses "spotlight", Ubuntu uses "grid", and Windows uses "menu". The arrow keys navigate every register (the grid and menu add left/right); Enter activates; Escape dismisses and restores focus.
The "menu" register is built on the 25H2 redesigned Start: one vertically scrollable page with Pinned at the top (two rows until “Show all”, remembered across opens), the Recent region in the middle, and the All apps section at the bottom. All apps renders in three switchable, persisted views: Category (the default, folder cards that open a flyout of the category’s apps), Grid (alphabetical icon groups with a click-to-jump letter picker), and List (compact rows). Apps group by the optional App.category; a category forms only with at least three apps, the rest fold into Other.
It carries the Windows Start personalization controls (Insider builds, May 2026) as chrome tokens: startMenuSize is "auto" (large on a wide viewport, small on a narrow one), "small" (6-column), or "large" (8-column); startMenuPinned, startMenuRecent, and startMenuAllApps show or hide each section independently; startMenuRecentFiles turns off file rows in Recent without hiding recently used apps; startMenuProfile hides the name and profile picture in the footer. The Recent section lists recently used apps plus the files contributed via registerRecentsSource. The Windows theme exposes all of these in its Settings Start section.
Import
Section titled “Import”import { Launcher, useLauncher, SPOTLIGHT_OPEN_EVENT } from "@react-ui-os/desktop";<Launcher> is mounted by the default <Desktop>. <Spotlight> is still exported as a back-compat alias of <Launcher>.
Opening the launcher
Section titled “Opening the launcher”| Trigger | Behavior |
|---|---|
Cmd-K (macOS) / Ctrl-K (elsewhere) | Toggles it. Bails when typing in an input or textarea. |
window.dispatchEvent(new CustomEvent(SPOTLIGHT_OPEN_EVENT)) | Opens from any component without prop drilling. |
| The dock launcher button | Dispatches the event above. The bar dock shows it; the menu and grid pop where the OS does. |
Result kinds
Section titled “Result kinds”| Kind | Source |
|---|---|
| App | Every entry in the apps prop passed to <Desktop> / <DesktopProvider>. |
| System | Every entry in the systemWindows registry (Settings by default, plus any you register). |
| External | Every result returned by a function registered via registerSpotlightSource. |
Activating an App or System result calls openWindow(...), the same primitive a dock click uses. External results run their own onActivate so they can navigate, dispatch, or open a URL.
Adding your own results
Section titled “Adding your own results”Use registerSystemWindow(...) when activating the result should open a window managed by react-ui-os. Settings is the canonical example.
Use registerSpotlightSource(...) when activating should do something else: navigate to a docs page, open a URL, dispatch an event. A source is (query) => SpotlightResult[], and each result owns its onActivate. Sources feed every presentation, not just Spotlight.
import { registerSpotlightSource } from "@react-ui-os/desktop";
useEffect(() => { return registerSpotlightSource("bookmarks", (query) => { if (!query) return []; return bookmarks .filter((b) => b.name.toLowerCase().includes(query)) .map((b) => ({ id: b.id, name: b.name, tagline: b.url, kindLabel: "Bookmark", onActivate: () => window.open(b.url, "_blank", "noopener"), })); });}, []);A custom launcher: useLauncher
Section titled “A custom launcher: useLauncher”When the three presentations do not fit, build your own face on the same brain. useLauncher() returns the open state, the query, the merged and filtered results, the selection, and activate / moveSelection / close. It also owns the Cmd-K toggle and the open event, so a custom launcher gets those for free. Render it at depth 2 in place of <Launcher>.
import { useLauncher } from "@react-ui-os/desktop";
function MyLauncher() { const { open, query, setQuery, results, activate, close } = useLauncher(); if (!open) return null; return ( <div role="dialog" aria-label="Launcher"> <input value={query} onChange={(e) => setQuery(e.target.value)} autoFocus /> {results.map((r) => ( <button key={r.key} onClick={() => activate(r)}> {r.name} </button> ))} <button onClick={close}>Close</button> </div> );}Accessibility
Section titled “Accessibility”- The search field is a
role="combobox"wired to its results listbox witharia-activedescendant, so screen readers announce the highlighted result as the arrows move it. - The Spotlight backdrop is
role="presentation"with click-to-dismiss; the panels arerole="dialog". - Each result is a
role="option"witharia-selected. - Focus restores on close and is never trapped.
See also
Section titled “See also”- Themes overview: the
chrome.launchertoken. - registerSpotlightSource: contribute findable rows.
- registerSystemWindow: surface custom windows.