# Layer

Gives `LayerPortal`s below the element their layer opened from (`useLayerRoot`'s `anchor`).

- Registry name: `layer` (`@candor/layer`)
- Tier: Free (MIT). No licence key needed.
- Docs: https://candor.themethodic.com/docs/layer

## Install

```bash
npx candor-ui add layer
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/layer`.
It brings in: tokens.

## Example

```tsx
"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>

Props:

- `anchor: React.RefObject<Element | null>`: The ref `useLayerRoot` returns as `anchor`: the trigger the layer last opened from, or the event's target.
- `children?: React.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.

Props:

- `portal: PortalPart<P>`: Base UI's portal part for this layer, such as `PopoverPrimitive.Portal`.
- `children?: React.ReactNode | undefined`

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

## Functions

### escapeHandledElsewhere

```ts
escapeHandledElsewhere(open: boolean, details: OpenChangeDetails)
```

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

### useLayerRoot

```ts
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={…}>`.

### layerHostFor

```ts
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.

```ts
const layerClass: "pointer-events-auto"
```

### LAYER_HOST

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

```ts
const LAYER_HOST: "data-candor-layer-host"
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
