---
title: Popover
description: "Displays rich content in a portal, triggered by a button."
---

```tsx
import * as stylex from '@stylexjs/stylex';

import { space, fontSize, fontWeight } from '@/lib/constants.stylex';

import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';

export default function PopoverDemo() {
  return (
    <Popover>
      <PopoverTrigger render={<Button variant="outline" />}>
        Open popover
      </PopoverTrigger>
      <PopoverContent>
        <div {...stylex.props(styles.form)}>
          <strong {...stylex.props(styles.heading)}>Dimensions</strong>
          <Input placeholder="Width" defaultValue="100%" />
          <Input placeholder="Height" defaultValue="25px" />
        </div>
      </PopoverContent>
    </Popover>
  );
}

const styles = stylex.create({
  form: {
    display: 'flex',
    flexDirection: 'column',
    gap: space.s2,
  },
  heading: {
    fontSize: fontSize.sm,
    fontWeight: fontWeight.semibold,
  },
});
```

## Install

```bash
npx shadcn@latest add @madeui/popover
```

## Usage

```tsx
import {
  Popover,
  PopoverContent,
  PopoverDescription,
  PopoverHeader,
  PopoverTitle,
  PopoverTrigger,
} from '@/components/ui/popover';
```

## Composition

```tsx
<Popover>
  <PopoverTrigger />
  <PopoverContent>
    <PopoverHeader>
      <PopoverTitle />
      <PopoverDescription />
    </PopoverHeader>
  </PopoverContent>
</Popover>
```

## Placement

```tsx
import { Button } from '@/components/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';

export default function PopoverPlacement() {
  return (
    <Popover>
      <PopoverTrigger render={<Button variant="outline" />}>
        Open above
      </PopoverTrigger>
      <PopoverContent side="top" align="start">
        Anchored to the top-start of the trigger.
      </PopoverContent>
    </Popover>
  );
}
```

## Header, title, description

`PopoverHeader` is a plain layout wrapper; `PopoverTitle` and `PopoverDescription` are Base UI's `Popover.Title` / `Popover.Description`, wiring up `aria-labelledby` / `aria-describedby` on the popup automatically.

```tsx
import { Button } from '@/components/ui/button';
import {
  Popover,
  PopoverContent,
  PopoverDescription,
  PopoverHeader,
  PopoverTitle,
  PopoverTrigger,
} from '@/components/ui/popover';

export default function PopoverParts() {
  return (
    <Popover>
      <PopoverTrigger render={<Button variant="outline" />}>
        Open popover
      </PopoverTrigger>
      <PopoverContent>
        <PopoverHeader>
          <PopoverTitle>Dimensions</PopoverTitle>
          <PopoverDescription>Set the dimensions for the layer.</PopoverDescription>
        </PopoverHeader>
      </PopoverContent>
    </Popover>
  );
}
```

## With form

```tsx
import * as stylex from '@stylexjs/stylex';

import { Button } from '@/components/ui/button';
import { Field, FieldLabel } from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import {
  Popover,
  PopoverContent,
  PopoverDescription,
  PopoverHeader,
  PopoverTitle,
  PopoverTrigger,
} from '@/components/ui/popover';
import { space } from '@/lib/constants.stylex';

export default function PopoverForm() {
  return (
    <Popover>
      <PopoverTrigger render={<Button variant="outline" />}>
        Edit profile
      </PopoverTrigger>
      <PopoverContent>
        <PopoverHeader>
          <PopoverTitle>Edit profile</PopoverTitle>
          <PopoverDescription>Update your display name and handle.</PopoverDescription>
        </PopoverHeader>
        <form {...stylex.props(styles.form)}>
          <Field>
            <FieldLabel htmlFor="popover-form-name">Name</FieldLabel>
            <Input id="popover-form-name" defaultValue="Evil Rabbit" />
          </Field>
          <Field>
            <FieldLabel htmlFor="popover-form-handle">Handle</FieldLabel>
            <Input id="popover-form-handle" defaultValue="@evilrabbit" />
          </Field>
          <Button type="submit" size="sm">
            Save
          </Button>
        </form>
      </PopoverContent>
    </Popover>
  );
}

const styles = stylex.create({
  form: {
    display: 'flex',
    flexDirection: 'column',
    gap: space.s3,
    marginTop: space.s4,
  },
});
```

## API reference

Built on [Base UI Popover](https://base-ui.com/react/components/popover). The tables below cover the props this library adds or changes — every other prop is forwarded to the underlying Base UI part; see the [Base UI Popover API reference](https://base-ui.com/react/components/popover#api-reference) for the full list.

### PopoverContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'bottom'` |  |
| `sideOffset` | `number` | `4` |  |
| `align` | `'start' \| 'center' \| 'end'` | `'center'` |  |
| `alignOffset` | `number` | `0` |  |
| `style` | `StyleXStyles` | — | StyleX styles merged last — always win over the component's own styles. |

`Popover` (Root), `PopoverTrigger`, and `PopoverClose` are Base UI parts re-exported unstyled — Root accepts `open` / `defaultOpen` / `onOpenChange`; Trigger accepts `render`.

### Styling

`PopoverHeader`, `PopoverTitle`, `PopoverDescription` accept `style` (`StyleXStyles`, merged last so caller overrides always win) plus all native props of the element they render.
