Skip to main content

Popover

  • Component overview: Click-triggered overlay container for more complex action content, descriptive content, or custom layouts.
  • Component group: Tips
  • Implementation note: PopoverContent retains the Radix positioning, Portal, and animation structure; the design layer owns the overlay card visuals.
  • Default interaction: children is the trigger element by default.
  • Figma spec

Basic Usage

Result
Loading...
Live Editor
render(
  <Popover content="Popover content">
    <Button variant="secondary" size="sm">
      Open Popover
    </Button>
  </Popover>,
)

Position Variants

Popover supports 8 primary positions controlled by combining side and align.

Result
Loading...
Live Editor
render(
  <div style={{ display: 'flex', flexDirection: 'column', gap: 24, padding: '48px 0' }}>
    <div style={{ display: 'flex', justifyContent: 'center', gap: 24 }}>
      {[
        { label: 'Top-Start', side: 'top', align: 'start' },
        { label: 'Top', side: 'top', align: 'center' },
        { label: 'Top-End', side: 'top', align: 'end' },
      ].map(({ label, side, align }) => (
        <Popover
          key={label}
          title={label}
          content="Popover content"
          side={side}
          align={align}
          defaultOpen
        >
          <Button variant="secondary" size="sm">
            {label}
          </Button>
        </Popover>
      ))}
    </div>

    <div style={{ display: 'flex', justifyContent: 'space-between', padding: '0 120px' }}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
        {[
          { label: 'Left-Start', side: 'left', align: 'start' },
          { label: 'Left', side: 'left', align: 'center' },
          { label: 'Left-End', side: 'left', align: 'end' },
        ].map(({ label, side, align }) => (
          <Popover
            key={label}
            title={label}
            content="Popover content"
            side={side}
            align={align}
            defaultOpen
          >
            <Button variant="secondary" size="sm">
              {label}
            </Button>
          </Popover>
        ))}
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
        {[
          { label: 'Right-Start', side: 'right', align: 'start' },
          { label: 'Right', side: 'right', align: 'center' },
          { label: 'Right-End', side: 'right', align: 'end' },
        ].map(({ label, side, align }) => (
          <Popover
            key={label}
            title={label}
            content="Popover content"
            side={side}
            align={align}
            defaultOpen
          >
            <Button variant="secondary" size="sm">
              {label}
            </Button>
          </Popover>
        ))}
      </div>
    </div>

    <div style={{ display: 'flex', justifyContent: 'center', gap: 24 }}>
      {[
        { label: 'Bottom-Start', side: 'bottom', align: 'start' },
        { label: 'Bottom', side: 'bottom', align: 'center' },
        { label: 'Bottom-End', side: 'bottom', align: 'end' },
      ].map(({ label, side, align }) => (
        <Popover
          key={label}
          title={label}
          content="Popover content"
          side={side}
          align={align}
          defaultOpen
        >
          <Button variant="secondary" size="sm">
            {label}
          </Button>
        </Popover>
      ))}
    </div>
  </div>,
)

Title and Content

Result
Loading...
Live Editor
render(
  <Popover
    title="Popover Title"
    content="Popover is suited for more complex interactive content, not just a single line of hint text."
  >
    <Button variant="tertiary" size="sm">
      Open
    </Button>
  </Popover>,
)

Custom Content Area

Result
Loading...
Live Editor
render(
  <Popover
    className="w-[320px]"
    content={
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        <div style={{ fontWeight: 600 }}>Custom Content</div>
        <div style={{ fontSize: 14, lineHeight: 1.6 }}>
          Custom nodes can be passed directly — the internal Popover shell is still used.
        </div>
      </div>
    }
  >
    <Button variant="tertiary" size="sm">
      Open Custom
    </Button>
  </Popover>,
)

Popover Props

PropTypeDefaultDescription
childrenReactNodeTrigger element. By default the popover uses children as the trigger.
contentReactNodeOverlay content
titleReactNodeOverlay title
side'top' | 'bottom' | 'left' | 'right''bottom'Popup direction
align'start' | 'center' | 'end''center'Alignment
sideOffsetnumber8Distance from the trigger element (px)
classNamestringCustom style class
asChildbooleantrueWhether to use children as the trigger itself