Popover

A panel of content attached to a control. The gap between Tooltip and Menu, and both of them were being pressed into the job.

Reference

sk-popover

Same platform machinery as Menu - the top layer, light dismiss on an outside click, Escape, and closing any other popover in the stack - but holding content rather than a list. A popover opened through popovertarget gets its invoker as an IMPLICIT anchor, so there is no anchor-name to declare and no per-instance inline style, which matters because a strict CSP forbids those. Where anchor positioning is unavailable the UA centres it in the viewport: still usable, still dismissible, just not attached.

<button class="sk-button" popovertarget="help">What is this?</button>

<div class="sk-popover" id="help" popover aria-labelledby="help-title">
  <p class="sk-popover-title" id="help-title">Build minutes</p>
  <div class="sk-popover-body">…</div>
</div>
AttributeTypeDefaultNotes
data-side"top" | "inline-end"block-endWhich edge of the trigger it opens from. It flips automatically when there is no room, so this is a preference rather than a promise.
data-align"end" | "center"startAlignment along the other axis. center uses anchor-center.
data-size"lg"22rem32rem, for a filter form rather than a paragraph.
popovertargetaction"hide"toggleOn a Done button inside the panel. Without it the button toggles, and a click on the trigger while the panel is open closes and immediately reopens it.

when to use which

Tooltip is one line of text on hover, described by aria-describedby - so its content is read as part of the control, and a button inside it is unreachable. Menu is a list of actions. Popover is arbitrary content opened by a click, with its own focus order. And none of them is a Modal: a popover does not trap focus and leaves the page live, which is right for a help bubble and wrong for a confirmation - if the reader must answer before doing anything else, that is a Modal.

<!-- interactive content belongs in a popover, never in a tooltip -->
AttributeTypeDefaultNotes
Tooltiphover, one line-Clipped by any scrolling ancestor, because it lives in a positioned wrapper rather than the top layer.
Menuclick, a list-Actions. Top layer, light dismiss.
Popoverclick, a panel-Content, including controls. Top layer, light dismiss, not modal.
Modalblocks the page-Focus trapped, the rest inert. For a decision that has to be made now.

Examples

Help bubble

Click the control, then click anywhere else. Nothing here is scripted.

You have used 412 build minutes.

Build minutes

Minutes are counted per build and rounded up, so a 20-second build costs one. Cached builds cost nothing.
Show markup
<div class="sk-flex sk-flex-wrap sk-items-center sk-gap-3">
  <p>
    You have used 412 build minutes.
  </p>
  <button class="sk-button" type="button" data-variant="quiet" data-size="sm" popovertarget="pop-help">
    What counts as a minute?
  </button>
  <div class="sk-popover" id="pop-help" popover aria-labelledby="pop-help-title">
    <p class="sk-popover-title" id="pop-help-title">
      Build minutes
    </p>
    Minutes are counted per build and rounded up, so a 20-second build costs one. Cached builds cost nothing.
  </div>
</div>

Interactive content

A form inside the panel. This is the case a Tooltip cannot serve at all - its text is part of the trigger's accessible name, so the controls inside would be unreachable.

Filter builds

Status
Show markup
<div class="sk-flex sk-flex-wrap sk-gap-3">
  <button class="sk-button" type="button" data-variant="neutral" data-size="md" popovertarget="pop-filter">
    <svg class="sk-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
      <circle cx="11" cy="11" r="7">
      </circle>
      <path d="m20 20-3.5-3.5">
      </path>
    </svg>
    Filter
  </button>
  <div class="sk-popover" id="pop-filter" popover data-size="lg" aria-labelledby="pop-filter-title">
    <p class="sk-popover-title" id="pop-filter-title">
      Filter builds
    </p>
    <div class="sk-popover-body">
      <fieldset class="sk-fieldset" id="pop-status">
        <legend class="sk-legend">
          Status
        </legend>
        <div class="sk-segmented">
          <label class="sk-segment">
            <input type="radio" id="pop-status-all" name="pop-status" value="all" checked>
            <span class="sk-segment-label">
              All
            </span>
          </label>
          <label class="sk-segment">
            <input type="radio" id="pop-status-passing" name="pop-status" value="passing">
            <span class="sk-segment-label">
              Passing
            </span>
          </label>
          <label class="sk-segment">
            <input type="radio" id="pop-status-failed" name="pop-status" value="failed">
            <span class="sk-segment-label">
              Failed
            </span>
          </label>
        </div>
      </fieldset>
      <div class="sk-switch">
        <input type="checkbox" role="switch" id="pop-mine" name="pop-mine">
        <span class="sk-switch-track" aria-hidden="true">
        </span>
        <span class="sk-switch-text">
          <label class="sk-switch-label" for="pop-mine">
            Only my builds
          </label>
        </span>
      </div>
    </div>
    <div class="sk-popover-footer">
      <button class="sk-button" type="button" data-variant="quiet" data-size="md" popovertarget="pop-filter" popovertargetaction="hide">
        Cancel
      </button>
      <button class="sk-button" type="button" data-variant="solid" data-size="md" popovertarget="pop-filter" popovertargetaction="hide">
        Apply
      </button>
    </div>
  </div>
</div>

Sides and alignment

Each opens from a different edge. They flip when there is no room, so drag the preview narrow and watch the last one change its mind.

The default: under the trigger, leading edges aligned.
Above the trigger.
On the trailing edge, centred against the trigger.
Centred on the trigger with anchor-center.
Show markup
<div class="sk-flex sk-flex-wrap sk-items-center sk-gap-3">
  <button class="sk-button" type="button" data-variant="neutral" data-size="md" popovertarget="pop-below">
    Below
  </button>
  <div class="sk-popover" id="pop-below" popover>
    The default: under the trigger, leading edges aligned.
  </div>
  <button class="sk-button" type="button" data-variant="neutral" data-size="md" popovertarget="pop-above">
    Above
  </button>
  <div class="sk-popover" id="pop-above" popover data-side="top">
    Above the trigger.
  </div>
  <button class="sk-button" type="button" data-variant="neutral" data-size="md" popovertarget="pop-end">
    Beside
  </button>
  <div class="sk-popover" id="pop-end" popover data-side="inline-end">
    On the trailing edge, centred against the trigger.
  </div>
  <button class="sk-button" type="button" data-variant="neutral" data-size="md" popovertarget="pop-center">
    Centred
  </button>
  <div class="sk-popover" id="pop-center" popover data-align="center">
    Centred on the trigger with anchor-center.
  </div>
</div>