Skip to content
Layers
Esc
navigateopen⌘Jpreview
On this page

Awaiting results

Await typed responses from a wired handle (or bag-form open), infer types via DataTag, or fire-and-forget with void.

Need a boolean back from a confirm? Wire the declaration, then await confirm.open(...)Promise<R> with no call-site generic. Awaiting is optional; fire-and-forget is equally first-class.

Typed await

Brand the contract once with layerOptions<P, R>(). The key carries a DataTag, so payload-only open infers R:

import { createLayer } from "@stainless-code/layers";

const confirm = layerOptions<ConfirmPayload, boolean>({
  stack: "confirm",
  key: ["confirm", "remove"],
  component: ConfirmDialog,
});

const c = createLayer(confirm, client);
const ok = await c.open({ title: "Remove?" });
//    ^? boolean

Adapters bind the same handle with useLayer / injectLayer / createLayer (see tabs below). You can also brand a key alone with layerKey<R>()(key) when options are inlined at the call site. See Identity and types.

Open from your framework

Adapters ship a wired handleuseLayer(options) (Svelte: createLayer; Angular: injectLayer) — for payload-only open plus reactive state/queued/top:

import { useLayer } from "@stainless-code/react-layers";

function RemoveButton() {
  const confirmLayer = useLayer(confirm);

  async function handleRemove() {
    const ok = await confirmLayer.open({ title: "Remove?" });
    //    ^? boolean
    if (ok) {
      /* … */
    }
  }

  return (
    <button type="button" onClick={() => void handleRemove()}>
      Remove
    </button>
  );
}
import { useLayer } from "@stainless-code/preact-layers";

function RemoveButton() {
  const confirmLayer = useLayer(confirm);

  async function handleRemove() {
    const ok = await confirmLayer.open({ title: "Remove?" });
    //    ^? boolean
    if (ok) {
      /* … */
    }
  }

  return (
    <button type="button" onClick={() => void handleRemove()}>
      Remove
    </button>
  );
}
import { useLayer } from "@stainless-code/solid-layers";

function RemoveButton() {
  const confirmLayer = useLayer(confirm);

  async function handleRemove() {
    const ok = await confirmLayer.open({ title: "Remove?" });
    //    ^? boolean
    if (ok) {
      /* … */
    }
  }

  return (
    <button type="button" onClick={() => void handleRemove()}>
      Remove
    </button>
  );
}
import { Component } from "@angular/core";
import { injectLayer } from "@stainless-code/angular-layers";

@Component({ /* … */ })
class RemoveButton {
  private confirmLayer = injectLayer(confirm);

  async remove() {
    const ok = await this.confirmLayer.open({ title: "Remove?" });
    //    ^? boolean
    if (ok) {
      /* … */
    }
  }
}
<script setup lang="ts">
import { useLayer } from "@stainless-code/vue-layers";

const confirmLayer = useLayer(confirm);

async function remove() {
  const ok = await confirmLayer.open({ title: "Remove?" });
  //    ^? boolean
  if (ok) {
    /* … */
  }
}
</script>
<script lang="ts">
  import { createLayer } from "@stainless-code/svelte-layers";

  const confirmLayer = createLayer(confirm);

  async function remove() {
    const ok = await confirmLayer.open({ title: "Remove?" });
    //    ^? boolean
    if (ok) {
      /* … */
    }
  }
</script>
<script lang="ts">
  import { createLayer } from "@stainless-code/svelte-layers/store";

  const confirmLayer = createLayer(confirm);

  async function remove() {
    const ok = await confirmLayer.open({ title: "Remove?" });
    //    ^? boolean
    if (ok) {
      /* … */
    }
  }
</script>

Bag-form (escape hatch)

client.open({ …layerOptions, payload }) stays valid — headless call sites, dynamic keys, or when you already hold a LayerClient:

const ok = await client.open({ ...confirm, payload: { title: "Remove?" } });
//    ^? boolean

See Migration — bag-form remains first-class alongside Drive handles.

Fire-and-forget

When a layer needs no return value, omit the response generic (defaults to void) and skip await:

import { createLayer } from "@stainless-code/layers";

const about = layerOptions({
  stack: "modal",
  key: ["about"],
  component: About,
});

const c = createLayer(about, client);
void c.open();

The layer dismisses itself — typically via call.dismiss() inside the component. Use void to signal intentional fire-and-forget when your linter flags floating promises.

Edge cases

  • loadFn errors reject the promise — see Error handling.
  • Validation failures reject with PayloadValidationError before mount — see Payload validation.
  • System teardown (cancelAll, parent-dismiss child drain, group dispose, host disconnect) rejects with LayerCancelledError — narrow with isLayerCancelledError; see Error handling.
  • Serial stacks queue later opens; the promise resolves only when the layer activates and ends — see Serial queues.

Last updated on July 19, 2026

Was this page helpful?