Skip to content
Tier

Layer

Free · MIT

Gives LayerPortals below the element their layer opened from (useLayerRoot's anchor), and, for a dialog-family root, tells the dialogs inside it that they are nested (useNestedDialog).

Markdown
Version 5.2.0· other versions

This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).

Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.8.0, 2.9.0, 2.10.0 and 3.0.0: the page came later, or had another name then.

Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.

Preview

Loading the preview

Install

With the Candor CLI
Open the starter in StackBlitz

Opens the free starter app in StackBlitz, in your browser, on stackblitz.com. Layer isn’t added for you: run npx candor-ui add layer in its terminal. The starter’s agent needs an Anthropic API key to chat; without one it shows the components only.

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/layer

Brings in: tokens

Usage

TSX · The example above
"use client"

import { Popover as PopoverPrimitive } from "@base-ui/react/popover"
import { LayerPortal, LayerRoot, layerClass, useLayerRoot } from "@/lib/candor/layer"

/**
 * A portalled part of your own, built the way Candor's are: the root guards Escape and remembers where it opened from,
 * the portal goes into a host when it opens inside another library's dialog, and every portalled part takes the pointer.
 */
function HelpTip({ label, children }: { label: string; children: React.ReactNode }) {
  const layer = useLayerRoot<PopoverPrimitive.Root.ChangeEventDetails>(undefined)
  return (
    <LayerRoot anchor={layer.anchor}>
      <PopoverPrimitive.Root onOpenChange={layer.onOpenChange}>
        <PopoverPrimitive.Trigger className="rounded-md border px-2 py-1 text-sm">{label}</PopoverPrimitive.Trigger>
        <LayerPortal portal={PopoverPrimitive.Portal}>
          <PopoverPrimitive.Positioner sideOffset={4} className={layerClass}>
            <PopoverPrimitive.Popup className={`w-64 rounded-md border bg-popover p-3 text-sm text-popover-foreground shadow-md ${layerClass}`}>
              {children}
            </PopoverPrimitive.Popup>
          </PopoverPrimitive.Positioner>
        </LayerPortal>
      </PopoverPrimitive.Root>
    </LayerRoot>
  )
}

export default function Example() {
  return (
    <HelpTip label="What is a run?">
      One request to an agent and everything it did to answer it: the steps, the tools it called and what they cost.
    </HelpTip>
  )
}

<LayerRoot>

PropTypeDefaultDescription
anchorrequiredReact.RefObject<Element | null>—The ref `useLayerRoot` returns as `anchor`: the trigger the layer last opened from, or the event's target.
dialogboolean | undefinedfalseThe root is a dialog's, a sheet's, a drawer's or an alert dialog's, so a dialog opened inside it is nested.
modalboolean | "trap-focus" | undefinedtrueThe dialog root's `modal`, when `dialog`: only a modal one (true, the default) blocks the page outside it, so only a modal nested dialog draws the backdrop that catches an outside click.
childrenReact.ReactNode | undefined—The layer's root and its parts.

<LayerPortal>

A layer's portal: Base UI's own (portal), into a host inside a foreign dialog when the layer opens from one, and where Base UI puts it otherwise (<body>, or the portal of the Candor layer it opened from). Its other props go to the portal.

PropTypeDefaultDescription
portalrequiredPortalPart<P>—Base UI's portal part for this layer, such as `PopoverPrimitive.Portal`.
childrenReact.ReactNode | undefined—What goes inside.

Also takes the props of Omit<P, "container" | "children">.

Functions

wrapTab(event: React.KeyboardEvent<HTMLElement>)

On a modal popup's onKeyDown: Tab from its last stop goes to its first, and Shift+Tab from its first (or from the popup itself) to its last, in the same task. Base UI's own wrap goes through a focus guard outside the popup and a frame later; this one never leaves the popup. Keys from a layer opened inside it (portalled elsewhere in the DOM) are left to that layer.

escapeHandledElsewhere(open: boolean, details: OpenChangeDetails)

Whether a close is an Escape that another library already handled (it called preventDefault()).

useLayerRoot(onOpenChange?: ((open: boolean, details: D) => void) | undefined)

For a layer's root: wraps onOpenChange so an Escape another library handled doesn't also close this layer, and remembers the element the layer opened from, for LayerPortal. Pass the result's onOpenChange to the Base UI root and wrap it in <LayerRoot anchor={…}>.

useNestedDialog()

Whether the dialog whose parts call this is a modal one opened inside another dialog, sheet, drawer or alert dialog. Base UI draws no backdrop for a nested dialog, so a click outside it lands on the outer one's backdrop and closes neither (KIT-45); the nested one draws a transparent backdrop of its own (nestedBackdrop), which takes the click and closes it alone, as Radix and the APG dialog pattern do. A nested dialog with modal={false} or "trap-focus" leaves the page outside it usable, so it is false there: a backdrop would catch the clicks meant for the outer one.

nestedBackdrop(nested: boolean)

The props for a dialog-family overlay: for a nested dialog (useNestedDialog), Base UI's forceRender and a transparent background, so the outer dialog's backdrop still dims the page once and an outside click closes only the innermost dialog; else nothing.

layerHostFor(from: Element | null | undefined)

The host a layer opened from from portals into: an existing one around it, one made in its foreign dialog, or none.

Values

layerClass

On every portalled part (backdrop, positioner, popup, viewport): a layer under a Radix modal still takes the pointer.

"pointer-events-auto"
LAYER_HOST

The attribute on a layer host. A layer opened from inside a host portals into the same host.

"data-candor-layer-host"