Why Cue
Programmatic, type-safe overlays you open from anywhere.
Cue turns React overlays into programmatic, type-safe interactions.
Define an overlay once, then open it from anywhere: a component, an effect, a request handler, another overlay, or any client-side code. Pass typed input, optionally await a typed result, and let Cue manage instances, stacking, backdrops, and lifecycle while your UI stays completely yours.
One overlay, many callers
Create one Cue environment and one typed confirm overlay. Any client-side caller can open it:
import { createCue } from "@vlkoss/cue";
export const cue = createCue();
export const confirmDelete = cue.createOverlay<{ organizationName: string }>((props, ctx) => (
<div role="dialog" aria-hidden={!ctx.open}>
<p>Delete {props.organizationName}?</p>
<button type="button" onClick={() => ctx.close()}>
Cancel
</button>
</div>
));
// table row
confirmDelete.open({ organizationName: row.name });
// command palette
confirmDelete.open({ organizationName: selected.name });The overlay definition stays in one module. Callers only need its handle and the props it accepts.
open() is not a hook
The handle is a plain object. open() works outside a component:
export const sessionExpired = cue.createOverlay((_props, ctx) => (
<div role="dialog" aria-hidden={!ctx.open}>
<p>Your session has expired.</p>
<button type="button" onClick={() => ctx.close()}>
Close
</button>
</div>
));
async function fetchProject(id: string) {
const response = await api.getProject(id);
if (response.status === 401) {
sessionExpired.open();
throw new Error("Unauthorized");
}
return response.data;
}The provider still has to be mounted once for that Cue instance. Call openAsync() when the caller needs a typed result. Dismissal resolves undefined.
When not to use Cue
If an overlay belongs to one screen and nothing else needs its instance, keep local state. Cue adds a store and provider for a sharing problem you do not have.