Skip to content
Esc
navigateopen⌘Jpreview
On this page

Popover

Displays rich content in a portal, triggered by a button.

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

npx shadcn@latest add @madeui/popover

Usage

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

Composition

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

Placement

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.

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

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

Was this page helpful?