cue

Customization

Configure a custom backdrop and reusable components for a Cue environment.

createCue() needs no options. Add a backdrop, a close delay, or shared components when your overlays need them.

Custom backdrops

Pass a component through backdrop. It is not a dialog. Cue portals it with the overlay instances.

import { createCue, type CueBackdrop } from "@vlkoss/cue";

const OverlayBackdrop: CueBackdrop = ({ open, close }) => (
  <div
    aria-hidden="true"
    data-open={open ? "" : undefined}
    className="fixed inset-0 bg-black/50"
    style={{ pointerEvents: open ? "auto" : "none" }}
    onClick={() => close({ strategy: "last" })}
  />
);

export const cue = createCue({
  backdrop: OverlayBackdrop,
  delay: 300,
});

open is true while the stack should show a dim, then false during the last overlay instance's close delay so the dim can fade out. Use { strategy: "last" } to close the top open overlay instance or { strategy: "all" } to close every open overlay instance.

An overlay definition can replace that dim, or opt out so a lower overlay instance keeps it:

const popover = cue.createOverlay(() => <Popover />, { backdrop: false });

Opened alone, that overlay instance has no dim. Opened over a dialog that uses the environment backdrop, the dialog's dim stays.

This matters when overlays stack. Rendering a backdrop inside each dialog creates one element per dialog and makes the stack darker as it grows. A provider-level backdrop keeps the stack predictable.

Reusable components

The components option accepts any application-defined map:

export const cue = createCue({
  components: {
    wrapper: OverlayWrapper,
    footer: OverlayFooter,
    closeButton: OverlayCloseButton,
  },
});

Cue does not decide what those keys mean or render them for you. Each overlay chooses its own composition:

const dialog = cue.createOverlay<{ message: string }>((props, ctx) => {
  const components = ctx.components;

  return (
    <components.wrapper>
      <p>{props.message}</p>
      <components.footer>
        <components.closeButton onClick={() => ctx.close()} />
      </components.footer>
    </components.wrapper>
  );
});

The component keys and their types come from the createCue() call. A different Cue instance can use a different map without a global registry or module augmentation.

Isolated environments

Each Cue owns its own definitions, instances, provider, components, backdrop, and lifecycle state:

const appCue = createCue();
const adminCue = createCue();

Opening an overlay from appCue has no effect on adminCue. Use separate instances when different parts of an application need independent overlay stacks.

On this page