# Popover

A click-opened floating surface for rich interactive content, anchored to a trigger and dismissed on click-outside or Escape.

```slint
import { Button } from "@glint/components/button.slint";
import { Popover } from "@glint/components/popover.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 320px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            trigger := Button {
                text: "View dimensions";
                haspopup: true;
                clicked => { pop.show(); }
            }
        }
    }

    pop := Popover {
        x: trigger.x;
        y: trigger.y + trigger.height + 6px;
        content-width: 260px;
        surface-label: "Dimensions";
        surface-description: "Configure width and height.";

        VerticalLayout {
            spacing: 8px;

            Text {
                text: "Width: 100%";
                color: Tokens.color-foreground;
                font-size: Tokens.typography-body-sm-size;
            }

            Text {
                text: "Height: 320px";
                color: Tokens.color-foreground;
                font-size: Tokens.typography-body-sm-size;
            }

            Button {
                text: "Done";
                clicked => { pop.close(); }
            }
        }
    }
}
```

## Usage

```slint
import { Button } from "@glint/components/button.slint";
import { Popover } from "@glint/components/popover.slint";

export component AppWindow inherits Window {
    VerticalLayout {
        alignment: center;

        trigger := Button {
            text: "Open Popover";
            haspopup: true;
            clicked => { pop.show(); }
        }
    }

    // Zero-size anchor: place it with x/y relative to your trigger.
    pop := Popover {
        x: trigger.x;
        y: trigger.y + trigger.height + 6px;
        content-width: 280px;

        VerticalLayout {
            // your popup content
        }
    }
}
```

`Popover` is a lightweight, click-opened floating surface built on Glint’s `Panel` primitive. Its `@children` slot holds whatever content you provide, while Glint manages the border, shadow, background styling, and native dismissable-layer behavior.

The `Popover` element acts as a zero-size anchor at its specified `x`/`y` coordinates. Opening it calls `show()` and closing it calls `close()`, mirroring Slint’s `PopupWindow` API.

## Examples

### Custom width

`content-width` sets the horizontal size of the floating surface (defaulting to 260px). The surface’s height automatically expands to fit its child content.

```slint
import { Button } from "@glint/components/button.slint";
import { Popover } from "@glint/components/popover.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 320px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            trigger := Button {
                text: "Custom width popover";
                haspopup: true;
                clicked => { pop.show(); }
            }
        }
    }

    pop := Popover {
        x: trigger.x - 40px;
        y: trigger.y + trigger.height + 6px;
        content-width: 340px;

        VerticalLayout {
            spacing: 8px;

            Text {
                text: "Extra wide layout for rich summaries, multi-column forms, or detailed parameter options.";
                color: Tokens.color-foreground;
                wrap: word-wrap;
                font-size: Tokens.typography-body-sm-size;
            }
        }
    }
}
```

### Surface placement and alignment with Panel

When you need placement vocabulary anchored to a trigger’s geometry, compose with the underlying `Panel` primitive directly. Setting `zone-width` and `zone-height` to match the trigger allows `surface-side` (`PanelSide.top`, `PanelSide.bottom`, `PanelSide.left`, `PanelSide.right`) and `surface-align` (`PanelAlign.start`, `PanelAlign.center`, `PanelAlign.end`) to position the surface automatically.

```slint
import { Button } from "@glint/components/button.slint";
import { Panel, PanelSide, PanelAlign } from "@glint/components/panel.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 320px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            trigger := Button {
                text: "Anchored panel";
                haspopup: true;
                clicked => { panel.show(); }
            }
        }
    }

    panel := Panel {
        x: trigger.x;
        y: trigger.y;
        zone-width: trigger.width;
        zone-height: trigger.height;
        surface-side: PanelSide.bottom;
        surface-align: PanelAlign.start;
        surface-gap: 6px;
        content-width: 240px;

        VerticalLayout {
            spacing: 8px;

            Text {
                text: "Positioned with PanelSide.bottom and PanelAlign.start.";
                color: Tokens.color-foreground;
                font-size: Tokens.typography-body-sm-size;
                wrap: word-wrap;
            }
        }
    }
}
```

### Focusing popup content with PanelFocusScope

Slint does not automatically route keyboard focus across `PopupWindow` boundaries. Wrapping interactive popup content in `PanelFocusScope` immediately hands focus to the popup when it appears so keyboard users can interact with its contents right away.

```slint
import { Button } from "@glint/components/button.slint";
import { Popover } from "@glint/components/popover.slint";
import { PanelFocusScope } from "@glint/components/panel.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 320px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            trigger := Button {
                text: "Quick actions";
                haspopup: true;
                clicked => { pop.show(); }
            }
        }
    }

    pop := Popover {
        x: trigger.x;
        y: trigger.y + trigger.height + 6px;
        content-width: 220px;

        PanelFocusScope {
            VerticalLayout {
                spacing: 8px;

                Button {
                    text: "Copy link";
                    clicked => { pop.close(); }
                }

                Button {
                    text: "Duplicate";
                    clicked => { pop.close(); }
                }
            }
        }
    }
}
```

## API Reference

### Properties

| Property              | Type        | Default    | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| --------------------- | ----------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `content-width`       | `in length` | `260px`    | Width of the surface; its height follows the content.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `surface-label`       | `in string` | no default | What the open surface announces itself as. Nothing is drawn from either — shadcn's PopoverTitle / PopoverDescription are content, and content is the consumer's (ADR-0006) — but without them an open popover is an unnamed rectangle to assistive technology. They are named for the \*surface\* rather than being `title` / `description`, and not only to stay clear of the string-only pair ADR-0006 removed: the anchor and the surface are two different things here, and only the second of them is what opens. `accessible-label` on the `Popover` element itself would describe the zero-size anchor, which is outside the PopupWindow and never on screen. |
| `surface-description` | `in string` | no default |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `is-open`             | `out bool`  | no default | True while the surface is on screen; mirrors `PopupWindow.is-open`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |

### Functions

| Function  | Description                                        |
| --------- | -------------------------------------------------- |
| `show()`  | Opens the surface. Mirrors `PopupWindow.show()`.   |
| `close()` | Closes the surface. Mirrors `PopupWindow.close()`. |

### Panel

The underlying floating surface primitive that handles PopupWindow anchoring, geometry, and dismissable layers.

### Properties

| Property              | Type            | Default             | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| --------------------- | --------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `content-width`       | `in length`     | `260px`             | Width of the floating surface; its height follows the content.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `content-padding`     | `in length`     | `Tokens.spacing-lg` | Inset between the surface's border and the slotted content. The spacing-lg default is the Popover chrome; content that draws its own inset (TimeGrid inside TimePicker) sets 0 to avoid double padding.                                                                                                                                                                                                                                                                                                                                                                                                           |
| `max-content-height`  | `in length`     | `0px`               | Ceiling on the whole surface, chrome included; `0` (the default) lets it grow with its content. The inset is the surface's own, so a rider capping itself no longer has to reach into `content-padding` to undo it — changing the panel's default inset used to silently change that one rider's height and nothing else's. A list whose rows are all one height wants `min(rows, n * row-height)` on its own scrolling surface instead — `Select` and `Command` cap themselves in rows, which is the unit a reader actually means.                                                                               |
| `is-open`             | `out bool`      | no default          | True while the surface is on screen; mirrors `PopupWindow.is-open`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `zone-width`          | `in length`     | `0px`               | A popup may claim more area than its surface occupies. An open `PopupWindow` takes every pointer event in the window wherever it sits, so a surface that has to react to the pointer resting on its \*trigger\* can only do it by covering that trigger — `Tooltip` reached the same conclusion from the other side (ADR-0007). The zone is the \*trigger's rectangle\*, in the anchor's own coordinates; the Panel unions it with wherever the surface lands and makes the popup that big. Zero width means no zone, and the popup is exactly the surface at the anchor — which is every other rider, unchanged. |
| `zone-height`         | `in length`     | `0px`               |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `surface-side`        | `in PanelSide`  | `PanelSide.bottom`  | Where the surface sits against that rectangle. This is the one placement vocabulary in the library that Glint resolves rather than the consumer, and it is confined to zone riders on purpose: placing a surface \*above\* its trigger means subtracting a height only the Panel can measure, because a `PopupWindow`'s insides may only be read from inside it. Without a zone there is no trigger rectangle to place against, and geometry stays the consumer's `x`/`y` (ADR-0006) — which is why `Popover` has no `side` and `HoverCard` does.                                                                 |
| `surface-align`       | `in PanelAlign` | `PanelAlign.start`  |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `surface-gap`         | `in length`     | `0px`               | Clearance between the trigger's edge and the surface.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `zone-hovered`        | `out bool`      | no default          | True while the pointer is inside the zone but not over the surface, which is what a zone exists to know. The surface sits above the zone's own TouchArea, so content inside it keeps its pointer events and reports its own hover; a rider ORs the two.                                                                                                                                                                                                                                                                                                                                                           |
| `surface-label`       | `in string`     | no default          | The name and description the open surface announces itself with. They cannot come from the rider's own element: that is a zero-size anchor \*outside\* the PopupWindow, so an `accessible-label` on it would describe the anchor rather than the surface that opened. A rider forwards its own properties here instead — `Popover` does.                                                                                                                                                                                                                                                                          |
| `surface-description` | `in string`     | no default          |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |

### Callbacks

| Callback         | Description                                                                                                                                                                                                                                      |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `zone-clicked()` | Fired when a click lands on the zone but outside the surface. The click cannot reach whatever is underneath — that is the price of covering it — so a rider should take the surface down and let the next click through, the way `Tooltip` does. |

### Functions

| Function  | Description                                        |
| --------- | -------------------------------------------------- |
| `show()`  | Shows the surface. Mirrors `PopupWindow.show()`.   |
| `close()` | Closes the surface. Mirrors `PopupWindow.close()`. |

### Enums

| Enum         | Values                           |
| ------------ | -------------------------------- |
| `PanelSide`  | `top`, `right`, `bottom`, `left` |
| `PanelAlign` | `start`, `center`, `end`         |

### PanelFocusScope

A FocusScope that immediately takes keyboard focus when the surface appears.

`PanelFocusScope` publishes no properties, callbacks or functions of its own. What a call site can set on it is the Slint `FocusScope` it inherits.

## Accessibility

- **Surface landmark.** The open popover surface exposes an accessible region landmark named by `surface-label` and described by `surface-description`.
- **Dismissable layers.** `Escape` and clicks outside the surface dismiss the popover. With nested or stacked layers, `Escape` closes only the topmost layer.
- **Focus restoration.** Dismissing the popover automatically restores keyboard focus to the trigger element that held focus before `show()` was called.
- **The trigger.** Controls that toggle a popover should declare `haspopup: true` on the trigger button.
