Skip to content

registerSpotlightSource

registerSpotlightSource lets a feature contribute rows to Spotlight without going through the app registry or the system-window registry. Use it for docs pages, bookmarks, recent files, search-as-you-type API results: anything where activating a row should run a callback rather than open a window.

import { registerSpotlightSource } from "@react-ui-os/desktop";
import type { SpotlightSource, SpotlightResult } from "@react-ui-os/desktop";
function registerSpotlightSource(id: string, source: SpotlightSource): () => void;

A Spotlight source returns the current set of results for a given query. Receives the raw query string (lowercased, trimmed) and returns the rows to merge into the Spotlight panel. Returning an empty array when the query doesn't apply is the convention.

type SpotlightSource = (query: string) => SpotlightResult[]
FieldTypeDescription
idstringStable id used as the React key and the dedup token.
namestringVisible row label.
tagline?stringOptional one-line subtitle on the right.
accent?stringAccent color tinting the row's tile gradient.
icon?ReactNodeOptional icon node rendered inside the tile.
kindLabel?stringSource-side label such as "Doc · Spotlight", "Preset · Tubes".
onActivate() => voidWhat to do when the row is activated (Enter or click).

id is a stable key: registering twice with the same id replaces the previous source. That makes it safe to call from a component that re-mounts.

The returned function unregisters the source. Call it in the cleanup of the useEffect that registered.

The query is the raw Spotlight input, already lowercased and trimmed. An empty query is the convention for “no input yet”; most sources return [] in that case so they do not flood the palette before the user has typed anything.

useEffect(() => {
return registerSpotlightSource("bookmarks", (query) => {
if (!query) return [];
return bookmarks
.filter((b) => b.name.toLowerCase().includes(query))
.map((b) => ({
id: `bookmark-${b.id}`,
name: b.name,
tagline: b.url,
kindLabel: "Bookmark",
accent: "#22c55e",
onActivate: () => window.open(b.url, "_blank", "noopener"),
}));
});
}, []);

Every result row shows a small accent tile, a name, an optional tagline on the right, and an optional kindLabel underneath the name. The icon prop is rendered inside the tile (use a tiny SVG, 16x16 or so). If no icon is provided the first letter of the name is rendered automatically.

{
id: "doc-window",
name: "Window",
tagline: "/components/window/",
kindLabel: "Docs · Component",
accent: "#7c66f5",
icon: <DocIcon />,
onActivate: () => router.push("/components/window/"),
}

Spotlight calls every registered source on every keystroke. A misbehaving source (one that throws while filtering) would otherwise take down the whole palette. To prevent that, sources are invoked inside a try/catch: throws are swallowed and the source contributes zero results for that keystroke. Errors are logged to the console in development.

This means you can register a source against unstable data without guarding every property access. But if a source consistently throws, the right fix is still to repair the source, not rely on the swallow.

The react-ui-os docs site registers a source so Cmd-K finds docs pages, not just apps. Activating a row escapes the playground iframe and navigates the parent docs frame to the matching path.

import { useEffect } from "react";
import { registerSpotlightSource } from "@react-ui-os/desktop";
const PAGES = [
{ name: "Window", path: "/components/window/" },
{ name: "Spotlight", path: "/components/spotlight/" },
// ...
];
export function DocsSpotlightSource() {
useEffect(() => {
return registerSpotlightSource("docs-pages", (query) => {
if (!query) return [];
const q = query.toLowerCase();
return PAGES.filter((p) => p.name.toLowerCase().includes(q)).map((p) => ({
id: p.path,
name: p.name,
tagline: p.path,
kindLabel: "Docs",
onActivate: () => {
if (window.top && window.top !== window) {
window.top.location.href = p.path;
} else {
window.location.href = p.path;
}
},
}));
});
}, []);
return null;
}

Mount it inside <Desktop> as a child so any provider context (useWindowManager, useTheme) is available if you need it:

<Desktop apps={apps} theme={theme}>
<DocsSpotlightSource />
</Desktop>

When to prefer registerSystemWindow instead

Section titled “When to prefer registerSystemWindow instead”

If activating the result should open a window that lives inside react-ui-os (chrome, traffic lights, dock indicator, minimize-to-genie, all of it), register a system window instead. Every entry in the system-window registry is automatically a Spotlight result, with openWindow(...) wired as the activator.

Reach for registerSpotlightSource when the activation is anything else: navigate, open a URL, dispatch an event, mutate app state.

  • Spotlight: the palette itself.
  • Desktop: the entry point that mounts Spotlight by default.
  • useWindowManager: for sources whose onActivate opens a managed window.