| name | lit-layers |
| description | Lit adapter for @stainless-code/lit-layers; open UI from anywhere and manage ordered, named stacks with typed or fire-and-forget `open` via Reactive Controllers and custom elements |
| license | MIT |
| keywords | ["tanstack-intent","lit","web-components","modal","dialog","stack","typescript"] |
| metadata | {"library":"@stainless-code/lit-layers","library_version":"0.2.3","framework":"lit"} |
| sources | ["https://stainless-code.com/layers/adapters/lit","https://github.com/stainless-code/layers/blob/main/docs/architecture.md"] |
Lit layer/stack UI with @stainless-code/lit-layers
Open any layer from anywhere and manage modal, dialog, drawer, popover, or toast UI as an ordered, named stack. @stainless-code/lit-layers is the Lit adapter for @stainless-code/layers; awaiting a typed result is optional, and fire-and-forget invocation (void client.open(...)) is equally first-class.
Named stacks, singletons with upsert and live update, serial queues, nested stacks, transitions, dismissal blockers, payload validation, and headless rendering each provide standalone value. The package re-exports @stainless-code/layers, so adapter and core APIs share one import path. For engine internals, use the layers core skill.
When to use this skill
- Reach for
@stainless-code/lit-layers to open overlay UI imperatively from anywhere — custom elements, controllers, or non-UI code — and manage it as an ordered, named stack instead of prop-drilling isOpen, lifting state, or threading onConfirm callbacks.
- It fits when you need any of: open/close from anywhere; await a typed result or fire-and-forget (both first-class); named/ordered stacks, singletons (
upsert) with live update, or one-at-a-time queues; nested stacks (useLayerGroup), enter/exit animations, dismissal guards (blockers), payload validation, or headless rendering (useStackHandles).
Skip it only when: you have a single, always-local overlay opened from one component, with no return, stacking, queue, animation, or guard needs and no wish for a global registry.
Full fit matrix: When to use Layers.
Install
bun add @stainless-code/lit-layers
Core is included. lit (>=3.2.0) and @lit/context (>=1.1.0) are required peers and should already be installed by the app.
Binding model
Binding: Reactive Controllers + requestUpdate. Host-first factories — useStack(this, { stack }), useLayer(this, options, client?). .current mirrors the selected snapshot and requests a host update on change. <stack-provider> is shadow + <slot>; outlet / subscribe / app-host are light DOM.
import { useStack } from "@stainless-code/lit-layers";
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
@customElement("open-count")
class OpenCount extends LitElement {
#stack = useStack(this, { stack: "confirm", select: (s) => s.length });
render() {
return html`<p>${this.#stack.current} open</p>`;
}
}
Declare → Mount → Call
import {
layerOptions,
type LayerCallContext,
} from "@stainless-code/lit-layers";
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
@customElement("confirm-dialog")
export class ConfirmDialog extends LitElement {
createRenderRoot() {
return this;
}
declare call: LayerCallContext<{ title: string }, boolean>;
declare payload: { title: string };
render() {
return html`<div role="dialog">
<h2>${this.payload.title}</h2>
<button @click=${() => void this.call.end(true)}>Yes</button>
<button @click=${() => void this.call.end(false)}>No</button>
</div>`;
}
}
export const confirm = layerOptions<{ title: string }, boolean>({
stack: "confirm",
key: ["confirm", "remove"],
component: ConfirmDialog,
exitingDelay: 200,
});
import {
defineStackElements,
provideLayerClient,
LayerClient,
} from "@stainless-code/lit-layers";
defineStackElements();
const client = new LayerClient();
<stack-provider .client="${client}">
<my-app></my-app>
<stack-outlet stack="confirm"></stack-outlet>
</stack-provider>
import { createLayer } from "@stainless-code/lit-layers";
import { confirm } from "./confirm-dialog";
const c = createLayer(confirm, client);
async function handleRemove() {
const ok = await c.open({ title: "Remove?" });
if (!ok) return;
deleteItem();
}
Low-level bag-form: client.open({ ...confirm, payload }).
provideLayerClient(host, client?) / <stack-provider .client=${...}> supplies the client to descendants via @lit/context; omit client on any hook to resolve it from context. useLayerClient(host) returns a controller whose .current throws when no provider has supplied a client.
Primitives
Options bag plus optional trailing LayerClient. Drive with useLayer(this, options, client?); observe with useLayerState(this, { key, ... }, client?).
| Export | Role |
|---|
useStack(host, { stack?, select?, compare? }, client?) | StackController<T> — .current mirrors the selected stack slice |
useQueuedStack(host, { ... }, client?) | StackController<T> over the queued snapshot |
useLayer(host, options, client?) | Drive — wired handle + open() / dismiss() + reactive state / queued / top |
useLayerState(host, { key, stack?, select?, compare? }, client?) | Observe — mounted same-key layers |
useLayerQueuedState(host, { key, ... }, client?) | Observe — queued same-key layers |
useLayer resolves the client lazily — omit client to resolve it from a provideLayerClient() / <stack-provider> ancestor after the host connects; the wired handle builds on first method access. Pass client explicitly for synchronous access at construction.
Ergonomic APIs
| Export | Role |
|---|
useStackHandles(host, stack?, rootProps?, client?) | { states, getCall } for headless rendering |
StackSubscribe CE | <stack-subscribe .selector .renderer> — .renderer(value) returns a TemplateResult |
useMutationFlow(host, call) | pending: boolean; run(fn).orEnd(response) |
useLayerGroup(host, call, options?, client?) | Child stack with outlet(): TemplateResult + stackId |
createStackHook(config?) | StackProvider, useAppStack(host), AppHost CE, AppLayer controller |
StackOutlet and LayerGroup.outlet() render each layer with its registered component — a LitElement constructor or a (props) => TemplateResult render function (no tag strings in v1). Id-keyed repeat keeps element instances stable across updates. defineStackElements() is idempotent and not auto-invoked on import.
Learn more