---
title: Popup
description: Low-level overlay primitive providing portal, backdrop, and content positioning.
sidebar:
  order: 1
search:
  tags: [overlay primitive, portal, positioning, modal]
---

# Popup

> Low-level overlay primitive providing portal, backdrop, and content positioning.

## Import

```tsx
import { Popup } from 'moraine'
```

## Slot Structure

Wrapper trigger with an optional backdrop and a floating content portal.

```text
trigger
├── overlay (optional)
└── content (portal)
```

## Examples

### Default Container

Popup provides only container + overlay. Content styling is fully custom.

```tsx
function DefaultContainer() {
  return (
    <Popup
      content={
        <div class="p-4 b-1 b-border rounded-xl bg-background ring-1 ring-foreground/10 shadow-md">
          <h3 class="text-sm font-semibold">Popup content</h3>
          <p class="text-sm text-muted-foreground mt-1">
            This content controls its own spacing and visuals.
          </p>
        </div>
      }
    >
      <Button>Open popup</Button>
    </Popup>
  )
}
```

### Dismiss Control

Block outside dismiss and count prevent-close attempts.

```tsx
function DismissControl() {
  const [preventedCloseCount, setPreventedCloseCount] = createSignal(0)

  return (
    <Popup
      dismissible={false}
      onClosePrevent={() => setPreventedCloseCount((value) => value + 1)}
      content={
        <div class="p-4 b-1 b-border rounded-xl bg-background ring-1 ring-foreground/10 shadow-md">
          <h3 class="text-sm font-semibold">Persistent popup</h3>
          <p class="text-sm text-muted-foreground mt-1">Refresh to dismiss</p>
          <p class="text-sm text-muted-foreground mt-1">
            Prevented close attempts: {preventedCloseCount()}
          </p>
        </div>
      }
    >
      <Button variant="secondary">Dismiss blocked</Button>
    </Popup>
  )
}
```

### Scrollable Overlay Mode

Scrollable overlay keeps content in flow while preserving the backdrop.

```tsx
function ScrollableOverlayMode() {
  const SCROLLABLE_LINES = Array.from({ length: 48 }, (_, index) => `Popup line ${index + 1}`)

  return (
    <Popup
      scrollable
      content={
        <div class="p-4 b-1 b-border rounded-xl bg-background ring-1 ring-foreground/10 shadow-md">
          <h3 class="text-sm font-semibold">Scrollable Popup</h3>
          <div class="mt-2 space-y-1">
            <For each={SCROLLABLE_LINES}>
              {(line) => <p class="text-sm text-foreground">{line}</p>}
            </For>
          </div>
        </div>
      }
    >
      <Button variant="outline">Open scrollable popup</Button>
    </Popup>
  )
}
```

## API Reference

### Attributes

#### `trigger`

Element users activate to open the popup.

#### `overlay`

Optional backdrop layer rendered behind popup content.

#### `content`

Positioned popup content panel.

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| aria-describedby | boolean \| string \| undefined | References descriptive text associated with the control. |
| aria-labelledby | boolean \| string \| undefined | References the element that labels the control or region. |
| aria-modal | boolean \| string \| undefined | Identifies modal content that traps interaction outside the dialog. |
| role | string | Defines the semantic role exposed to assistive technology. |

### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| children | JSX.Element | — | Element that triggers the popup or additional content. |
| class | ClassValue | — | Class applied to the component root or trigger element. |
| classes | PopupT.Classes \| undefined | — | — |
| content | ComponentOrElement<ModalContentContext> \| undefined | — | Modal content rendered inside the content surface. |
| defaultOpen | boolean \| undefined | — | Initial open state when uncontrolled. |
| dismissible | boolean \| undefined | — | Whether outside interaction and Escape should dismiss the shell. |
| fullscreen | boolean \| undefined | false | Whether the popup should cover the entire viewport. |
| id | string \| undefined | — | Unique identifier used to derive the content id. |
| layout | "default" \| "scrollable" \| "fullscreen" \| undefined | — | — |
| onClosePrevent | (() => void) \| undefined | — | Called when a dismissal attempt is blocked. |
| onOpenChange | ((open: boolean) => void) \| undefined | — | Called whenever the open state changes. |
| open | boolean \| undefined | — | Controlled open state. |
| overlay | boolean \| undefined | — | Whether to render the overlay element. |
| ref | JSX.HTMLElementTags["span"] extends { ref?: infer Ref; } ? Ref : never \| undefined | — | — |
| scrollable | boolean \| undefined | false | Whether to allow scrolling within the popup. |
| style | JSX.CSSProperties \| undefined | — | — |
| styles | PopupT.Styles \| undefined | — | — |

### ARIA

Accessibility attributes and roles emitted by the component markup.

| Attribute | Type | Description |
| --- | --- | --- |
| aria-describedby | boolean \| string \| undefined | References descriptive text associated with the control. |
| aria-labelledby | boolean \| string \| undefined | References the element that labels the control or region. |
| aria-modal | boolean \| string \| undefined | Identifies modal content that traps interaction outside the dialog. |
| role | string | Defines the semantic role exposed to assistive technology. |

### Data Attributes

State and slot attributes exposed for styling hooks and selectors.

| Attribute | Type | Description |
| --- | --- | --- |
| data-slot | string | Identifies the rendered slot for styling hooks and selectors. |
