# Popover

A small raised panel that opens from a button and stays beside it, holding a title, a few lines or a field, and actions, without blocking the page.

Block: `smbk-popover`. Status: candidate. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Desktop and mobile: differ by presentation only.

## Options

| Axis | Values | Default | Meaning |
|---|---|---|---|
| `align` | `start`, `end` | `start` | Which edge of the opener the panel lines up with. End keeps a popover at the right of a bar inside the window. |
| `side` | `below`, `above`, `left`, `right` | `below` | The side of the opener the panel prefers. With script it takes the opposite side when this one has no room, as at the foot of the window, and below when neither the left nor the right has room, as on a phone. |
| `pointer` | on / off | off | A small triangle on the panel's edge, pointing at the middle of what opened it. |

Parts: `smbk-popover__anchor`, `smbk-popover__layer`, `smbk-popover__scroll`, `smbk-popover__pointer`, `smbk-popover__header`, `smbk-popover__heading`, `smbk-popover__icon`, `smbk-popover__titles`, `smbk-popover__title`, `smbk-popover__description`, `smbk-popover__close`, `smbk-popover__divider`, `smbk-popover__body`, `smbk-popover__footer`, `smbk-popover__helper`

## Markup

```html
<div class="smbk-popover__anchor">
  <button
    type="button"
    class="smbk-btn smbk-btn--tone-outline smbk-btn--size-md"
    id="popover-1-opener"
    aria-haspopup="dialog"
    aria-expanded="true"
    aria-controls="popover-1-panel"
  >
    <span class="smbk-btn__label">Version history</span>
    <i class="ph ph-caret-down smbk-icon smbk-icon--size-md smbk-btn__icon" aria-hidden="true"></i>
  </button>
  <div class="smbk-popover__layer">
    <div
      id="popover-1-panel"
      role="dialog"
      aria-labelledby="popover-1-title"
      class="smbk-popover smbk-popover--align-start smbk-popover--side-below"
      tabindex="-1"
      data-scrollbar=""
    >
      <div class="smbk-popover__header">
        <div class="smbk-popover__heading">
          <div class="smbk-popover__titles">
            <p id="popover-1-title" class="smbk-popover__title">Version history</p>
          </div>
          <button
            type="button"
            class="smbk-btn smbk-btn--tone-ghost smbk-btn--size-sm smbk-btn--icon-only smbk-popover__close"
            aria-label="Close"
          >
            <i class="ph ph-x smbk-icon smbk-icon--size-md smbk-btn__icon" aria-hidden="true"></i>
          </button>
        </div>
      </div>
      <div class="smbk-popover__body">
        <p class="smbk-popover__description">Every save is kept for thirty days. Restore one to bring the page back as it was.</p>
      </div>
      <div class="smbk-popover__footer">
        <hr class="smbk-divider smbk-divider--orientation-horizontal smbk-popover__divider"/>
        <button type="button" class="smbk-btn smbk-btn--tone-outline smbk-btn--size-sm">
          <span class="smbk-btn__label">Learn more</span>
          <i class="ph ph-info smbk-icon smbk-icon--size-md smbk-btn__icon" aria-hidden="true"></i>
        </button>
      </div>
    </div>
  </div>
</div>
```

## React

```tsx
import { Popover, PopoverHeader, PopoverBody, PopoverFooter } from './design-system/components/popover/popover.js';
import { Button } from './design-system/components/button/button.js';

<Popover align="start" side="below" defaultOpen trigger={<Button tone="outline" size="md" trailingIcon="CaretDown">Version history</Button>}>
  <PopoverHeader title="Version history" closeButton />
  <PopoverBody>Every save is kept for thirty days. Restore one to bring the page back as it was.</PopoverBody>
  <PopoverFooter>
    <Button tone="outline" size="sm" trailingIcon="Info">Learn more</Button>
  </PopoverFooter>
</Popover>
```

## Stylesheet

```css
/* smbk-popover — Popover. Figma "Popover" 4804:65705, over "PopoverHeader"
 * 4804:65619, "PopoverBody" 4804:65655 and "PopoverFooter" 4803:65513.
 * A panel on the overlay surface in three sections spaced by the large stack step: a header
 * (an optional icon, the title, an optional description, an optional close
 * button and an optional divider), a body of text or a field, and a footer
 * (a divider, then buttons at the end or a line of helper text). The width is
 * the popover width token, carried from Figma's frame, and never more than the
 * room it is given.
 * With an opener, the anchor holds the opener and the layer, and the layer the
 * panel, which hangs under the opener (or over it, on the above side) as the
 * dropdown's does, the same gap away, or beside it on the left and right sides.
 * With script, the layer is lifted over the page while the panel shows and the
 * wrapper places the panel on the side it prefers, or the other when that one has
 * no room (floating.ts). With the pointer flag, what the panel holds sits in the
 * scroll part and the pointer beside it, on the edge facing the opener. */

.smbk-popover {
  box-sizing: border-box;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-stack-lg);
  width: var(--smbk-size-popover-width);
  max-width: 100%;
  margin: 0;
  padding: var(--smbk-space-inset-xl);
  border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-rim);
  border-radius: var(--smbk-radius-container-sm);
  background: var(--smbk-color-surface-overlay);
  color: var(--smbk-color-text-default);
  box-shadow: var(--smbk-elevation-overlay);
  text-align: start;
  /* Its words wrap wherever it opens: a table heading or a bar keeps its own on one line, and the panel would take that
     and scroll sideways. */
  white-space: normal;
}

/* The panel takes focus as it opens when it holds no field, as a container, not a control: no ring of its own. */
.smbk-popover:focus {
  outline: none;
}

/* ---- header ---- */

.smbk-popover__header {
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-stack-md);
}

.smbk-popover__heading {
  display: flex;
  align-items: flex-start;
  gap: var(--smbk-space-inline-md);
}

/* The words keep clear of the close button, which hangs past the row's end (below). */
.smbk-popover__heading:has(.smbk-popover__close) {
  padding-inline-end: calc(var(--smbk-size-control-sm) - (var(--smbk-space-inset-xl) - var(--smbk-space-inset-sm)) + var(--smbk-space-inline-md));
}

/* As tall as the title's line, so the icon sits on its middle. */
.smbk-popover__icon {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  height: calc(var(--smbk-typography-label-lg-font-size) * var(--smbk-typography-label-lg-line-height));
  color: var(--smbk-color-text-subtle);
  font-size: var(--smbk-size-icon-sm);
}

/* The title sits straight on its description: the line heights give the air. */
.smbk-popover__titles {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--smbk-space-text-label);
  min-width: 0;
}

.smbk-popover__title {
  display: block;
  margin: 0;
  color: var(--smbk-color-text-default);
  font-family: var(--smbk-typography-label-lg-font-family);
  font-size: var(--smbk-typography-label-lg-font-size);
  font-weight: var(--smbk-typography-label-lg-font-weight);
  line-height: var(--smbk-typography-label-lg-line-height);
  letter-spacing: var(--smbk-typography-label-lg-letter-spacing);
}

.smbk-popover__description {
  margin: 0;
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-body-sm-font-family);
  font-size: var(--smbk-typography-body-sm-font-size);
  font-weight: var(--smbk-typography-body-sm-font-weight);
  line-height: var(--smbk-typography-body-sm-line-height);
  letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
}

/* The close button is centred on the title's first line, its end edge the small inset from the panel's
   end edge, over the padding rather than in the row, as Figma places it. Its cross is the subtlest text
   color, as Figma binds it. */
.smbk-popover .smbk-popover__close {
  position: absolute;
  top: calc(var(--smbk-space-inset-xl) + (var(--smbk-typography-label-lg-font-size) * var(--smbk-typography-label-lg-line-height) - var(--smbk-size-control-sm)) / 2);
  inset-inline-end: var(--smbk-space-inset-sm);
  color: var(--smbk-color-text-subtlest);
}

.smbk-popover__divider {
  margin: 0;
}

/* ---- body: a few lines in the description's look, a field, or both ---- */

.smbk-popover__body {
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-stack-md);
}

/* ---- footer: a divider, then actions at the end or helper text ---- */

.smbk-popover__footer {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--smbk-space-stack-md);
}

.smbk-popover__footer > .smbk-popover__divider,
.smbk-popover__helper {
  align-self: stretch;
}

.smbk-popover__helper {
  margin: 0;
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-body-xs-font-family);
  font-size: var(--smbk-typography-body-xs-font-size);
  font-weight: var(--smbk-typography-body-xs-font-weight);
  line-height: var(--smbk-typography-body-xs-line-height);
  letter-spacing: var(--smbk-typography-body-xs-letter-spacing);
}

/* ---- the opener: the anchor holds the opener and the layer, the layer the panel hanging under the opener ----
   The layer, its lift into the top layer, the closed panel and the panel's motion (as the dropdown's panel, it drops
   in from its opener and fades, rises when it opened above, and does not travel under reduced motion) are the
   floating panels' own, shared with the dropdown and the tooltip: floating/floating.css. */

.smbk-popover__anchor {
  position: relative;
  display: inline-flex;
}

/* Words as the opener stay in their line: a flex box would make them a box of their own, which does not break with
   the words around it. An inline box ends at the words, so the padding takes in their line for the panel to hang
   under; it moves nothing in the line. */
.smbk-popover__anchor:has(> .smbk-link--pressable) {
  display: inline;
  padding-block-end: var(--smbk-space-link-underline);
}

/* Until the wrapper lifts the layer, the panel hangs from the anchor, as in markup without the layer. Hanging from its
   opener, the panel is never wider than the window less the extra-large inset on either side, so on the narrowest
   phones it narrows rather than make the page scroll sideways. */
.smbk-popover__anchor > .smbk-popover,
.smbk-popover__layer > .smbk-popover {
  position: absolute;
  top: calc(100% + var(--smbk-space-stack-xs));
  left: 0;
  z-index: var(--smbk-layer-overlay);
  max-width: calc(100vw - 2 * var(--smbk-space-inset-xl));
}

.smbk-popover__anchor > .smbk-popover--align-end,
.smbk-popover__layer > .smbk-popover--align-end {
  right: 0;
  left: auto;
}

/* Preferring the side above, it hangs over the opener until the wrapper places it. */
.smbk-popover__anchor > .smbk-popover--side-above,
.smbk-popover__layer > .smbk-popover--side-above {
  top: auto;
  bottom: calc(100% + var(--smbk-space-stack-xs));
}

/* Preferring the left or the right, it hangs beside the opener, its top lined up with the opener's, or its foot when
   lined up with the end, until the wrapper places it. */
.smbk-popover__anchor > :is(.smbk-popover--side-left, .smbk-popover--side-right),
.smbk-popover__layer > :is(.smbk-popover--side-left, .smbk-popover--side-right) {
  top: 0;
  bottom: auto;
}

.smbk-popover__anchor > .smbk-popover--side-right,
.smbk-popover__layer > .smbk-popover--side-right {
  right: auto;
  left: calc(100% + var(--smbk-space-stack-xs));
}

.smbk-popover__anchor > .smbk-popover--side-left,
.smbk-popover__layer > .smbk-popover--side-left {
  right: calc(100% + var(--smbk-space-stack-xs));
  left: auto;
}

.smbk-popover__anchor > .smbk-popover--align-end:where(.smbk-popover--side-left, .smbk-popover--side-right),
.smbk-popover__layer > .smbk-popover--align-end:where(.smbk-popover--side-left, .smbk-popover--side-right) {
  top: auto;
  bottom: 0;
}

/* Placed in the lifted layer, it is never taller than the room on its side of the opener (the wrapper sets that
   height), and a panel taller than that scrolls inside itself, with the system's scrollbar over it (scrollbar.ts), or
   the browser's thin one in markup written by hand. */
.smbk-popover__layer:popover-open > .smbk-popover {
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--smbk-color-border-subtle) transparent;
}

/* ---- the pointer: a small triangle on the edge facing the opener ----
   A panel that scrolled would cut it off, so with the pointer the panel keeps what sticks out past its edge and what it
   holds scrolls in the scroll part, which takes the panel's padding and its gap. */

.smbk-popover--pointer {
  --smbk-popover-pointer-base: calc(var(--smbk-size-icon-lg) - 2 * var(--smbk-border-width-hairline));
  --smbk-popover-pointer-depth: calc(var(--smbk-popover-pointer-base) / 2);
  gap: 0;
  padding: 0;
}

.smbk-popover__scroll {
  box-sizing: border-box;
  position: relative;
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--smbk-space-stack-lg);
  min-height: 0;
  padding: var(--smbk-space-inset-xl);
  border-radius: inherit;
}

/* The keyboard reaches it while it scrolls (the wrapper makes it a region then): its ring is drawn inside its edge. */
.smbk-popover__scroll:focus-visible {
  outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
  outline-offset: calc(-1 * var(--smbk-focus-ring-width));
}

.smbk-popover__layer:popover-open > .smbk-popover--pointer {
  overflow: visible;
}

.smbk-popover__layer:popover-open > .smbk-popover--pointer > .smbk-popover__scroll {
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--smbk-color-border-subtle) transparent;
}

/* Hanging from its opener, the panel stands the pointer's reach further off, as the wrapper places it. These weigh no
   more than the other hanging rules, so a lifted panel still takes only the place the wrapper writes (floating.css). */
.smbk-popover__anchor > .smbk-popover--pointer:where(.smbk-popover--side-below),
.smbk-popover__layer > .smbk-popover--pointer:where(.smbk-popover--side-below) {
  top: calc(100% + var(--smbk-space-stack-xs) + var(--smbk-popover-pointer-depth) - var(--smbk-border-width-hairline));
}

.smbk-popover__anchor > .smbk-popover--pointer:where(.smbk-popover--side-above),
.smbk-popover__layer > .smbk-popover--pointer:where(.smbk-popover--side-above) {
  bottom: calc(100% + var(--smbk-space-stack-xs) + var(--smbk-popover-pointer-depth) - var(--smbk-border-width-hairline));
}

.smbk-popover__anchor > .smbk-popover--pointer:where(.smbk-popover--side-right),
.smbk-popover__layer > .smbk-popover--pointer:where(.smbk-popover--side-right) {
  left: calc(100% + var(--smbk-space-stack-xs) + var(--smbk-popover-pointer-depth) - var(--smbk-border-width-hairline));
}

.smbk-popover__anchor > .smbk-popover--pointer:where(.smbk-popover--side-left),
.smbk-popover__layer > .smbk-popover--pointer:where(.smbk-popover--side-left) {
  right: calc(100% + var(--smbk-space-stack-xs) + var(--smbk-popover-pointer-depth) - var(--smbk-border-width-hairline));
}

/* In a narrow space there is no room beside the opener: it hangs under it, as the wrapper places it. */
@container style(--smbk-layout: narrow) {
  .smbk-popover__anchor > :is(.smbk-popover--side-left, .smbk-popover--side-right),
  .smbk-popover__layer > :is(.smbk-popover--side-left, .smbk-popover--side-right) {
    top: calc(100% + var(--smbk-space-stack-xs));
    right: auto;
    bottom: auto;
    left: 0;
  }

  .smbk-popover__anchor > :is(.smbk-popover--side-left, .smbk-popover--side-right) > .smbk-popover__pointer,
  .smbk-popover__layer:not(:popover-open) > :is(.smbk-popover--side-left, .smbk-popover--side-right):not([data-side]) > .smbk-popover__pointer {
    display: none;
  }
}

/* Its box is the triangle's, as deep as its shape, which the wrapper measures. It stands on the panel's inside edge, so
   it covers the hairline there and the two read as one shape. Its own edge is the strong border: the panel's rim is
   nearly the panel's color, and with no shadow the triangle would be lost on a light page. The fill is the same
   triangle moved in by a hairline across its slopes, so the edge shows only along them. */
.smbk-popover__pointer {
  position: absolute;
  width: var(--smbk-popover-pointer-base);
  height: var(--smbk-popover-pointer-depth);
  background: var(--smbk-color-border-strong);
}

.smbk-popover__pointer::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--smbk-color-surface-overlay);
  clip-path: inherit;
}

/* The side it is on: the one the wrapper placed it on, or until then the one asked for. */
.smbk-popover--pointer:is([data-side="below"], .smbk-popover--side-below:not([data-side])) > .smbk-popover__pointer {
  bottom: 100%;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}

.smbk-popover--pointer:is([data-side="below"], .smbk-popover--side-below:not([data-side])) > .smbk-popover__pointer::after {
  translate: 0 calc(var(--smbk-border-width-hairline) * 1.4142);
}

.smbk-popover--pointer:is([data-side="above"], .smbk-popover--side-above:not([data-side])) > .smbk-popover__pointer {
  top: 100%;
  clip-path: polygon(50% 100%, 0 0, 100% 0);
}

.smbk-popover--pointer:is([data-side="above"], .smbk-popover--side-above:not([data-side])) > .smbk-popover__pointer::after {
  translate: 0 calc(var(--smbk-border-width-hairline) * -1.4142);
}

.smbk-popover--pointer:is([data-side="right"], .smbk-popover--side-right:not([data-side])) > .smbk-popover__pointer {
  right: 100%;
  width: var(--smbk-popover-pointer-depth);
  height: var(--smbk-popover-pointer-base);
  clip-path: polygon(0 50%, 100% 0, 100% 100%);
}

.smbk-popover--pointer:is([data-side="right"], .smbk-popover--side-right:not([data-side])) > .smbk-popover__pointer::after {
  translate: calc(var(--smbk-border-width-hairline) * 1.4142) 0;
}

.smbk-popover--pointer:is([data-side="left"], .smbk-popover--side-left:not([data-side])) > .smbk-popover__pointer {
  left: 100%;
  width: var(--smbk-popover-pointer-depth);
  height: var(--smbk-popover-pointer-base);
  clip-path: polygon(100% 50%, 0 0, 0 100%);
}

.smbk-popover--pointer:is([data-side="left"], .smbk-popover--side-left:not([data-side])) > .smbk-popover__pointer::after {
  translate: calc(var(--smbk-border-width-hairline) * -1.4142) 0;
}

/* Where along the edge: until it is placed, the inset in from the end the panel lines up with. */
.smbk-popover--pointer:is([data-side="below"], [data-side="above"], .smbk-popover--side-below:not([data-side]), .smbk-popover--side-above:not([data-side])) > .smbk-popover__pointer {
  left: var(--smbk-space-inset-xl);
}

.smbk-popover--pointer.smbk-popover--align-end:is([data-side="below"], [data-side="above"], .smbk-popover--side-below:not([data-side]), .smbk-popover--side-above:not([data-side])) > .smbk-popover__pointer {
  right: var(--smbk-space-inset-xl);
  left: auto;
}

.smbk-popover--pointer:is([data-side="left"], [data-side="right"], .smbk-popover--side-left:not([data-side]), .smbk-popover--side-right:not([data-side])) > .smbk-popover__pointer {
  top: var(--smbk-space-inset-xl);
}

.smbk-popover--pointer.smbk-popover--align-end:is([data-side="left"], [data-side="right"], .smbk-popover--side-left:not([data-side]), .smbk-popover--side-right:not([data-side])) > .smbk-popover__pointer {
  top: auto;
  bottom: var(--smbk-space-inset-xl);
}

/* Placed, the wrapper writes the place of its middle, measured from the panel's outside edge, where the pointer's own
   place is measured from inside it: the hairline between the two is taken back here. */
.smbk-popover--pointer[data-placed]:is([data-side="below"], [data-side="above"]) > .smbk-popover__pointer {
  right: auto;
  translate: calc(-50% - var(--smbk-border-width-hairline)) 0;
}

.smbk-popover--pointer[data-placed]:is([data-side="left"], [data-side="right"]) > .smbk-popover__pointer {
  bottom: auto;
  translate: 0 calc(-50% - var(--smbk-border-width-hairline));
}

/* ---- high contrast (forced colors) ---- */
/* The browser drops fills there: drawn in the user's own colors, the pointer points again and keeps its edge. */

@media (forced-colors: active) {
  .smbk-popover__pointer {
    background: CanvasText;
  }

  .smbk-popover__pointer::after {
    background: Canvas;
  }
}
```

## Tokens it reads

```json
{
  "motion": {
    "enter": {
      "duration": {
        "$type": "duration",
        "$value": "{{duration.320}}"
      },
      "easing": {
        "$type": "cubicBezier",
        "$value": "{{easing.expo-out}}"
      }
    },
    "exit": {
      "duration": {
        "$type": "duration",
        "$value": "{{duration.200}}"
      },
      "easing": {
        "$type": "cubicBezier",
        "$value": "{{easing.expo-in}}"
      }
    }
  },
  "space": {
    "stack": {
      "xs": {
        "$type": "dimension",
        "$value": "{{space.4}}",
        "$description": "Gap between things stacked one above another, extra small."
      },
      "lg": {
        "$type": "dimension",
        "$value": "{{space.16}}",
        "$description": "Gap between things stacked one above another, large."
      },
      "md": {
        "$type": "dimension",
        "$value": "{{space.12}}",
        "$description": "Gap between things stacked one above another, medium."
      }
    },
    "inset": {
      "xl": {
        "$type": "dimension",
        "$value": "{{space.16}}",
        "$description": "Padding inside a component, extra large."
      },
      "sm": {
        "$type": "dimension",
        "$value": "{{space.6}}",
        "$description": "Padding inside a component, small."
      }
    },
    "inline": {
      "md": {
        "$type": "dimension",
        "$value": "{{space.8}}",
        "$description": "Gap between things side by side, medium."
      }
    },
    "text": {
      "label": {
        "$type": "dimension",
        "$value": "{{space.0}}",
        "$description": "Between a label or title and the line of supporting text right under it, inside a component. None: untrimmed line height already spaces the two lines."
      }
    },
    "link-underline": {
      "$type": "dimension",
      "$value": "{{space.4}}",
      "$description": "Gap between a link's words and the line under them. Figma sets a text's underline as a plain number with no variable behind it."
    }
  },
  "size": {
    "popover": {
      "width": {
        "$type": "dimension",
        "$value": "320px",
        "$description": "Width of the popover panel. A fixed frame width in Figma with no variable behind it; it should become one."
      }
    },
    "control": {
      "sm": {
        "$type": "dimension",
        "$value": "{{size.32}}",
        "$description": "Height of a small control: 32 comfortable, 28 compact. Dashboard recipe: use compact density; 40 for the page’s one main action and for forms; 32 for toolbars and filters, the size used most; 28 for actions inside table rows and cards. On phones switch to comfortable density, or give small controls an invisible 44 px tap area (size/target/comfortable)."
      }
    },
    "icon": {
      "sm": {
        "$type": "dimension",
        "$value": "{{size.16}}",
        "$description": "Icon box, small."
      },
      "lg": {
        "$type": "dimension",
        "$value": "{{size.24}}",
        "$description": "Icon box, large."
      }
    }
  },
  "border-width": {
    "hairline": {
      "$type": "dimension",
      "$value": "{{border-width.1}}",
      "$description": "Thin lines: dividers and almost every edge."
    }
  },
  "color": {
    "border": {
      "rim": {
        "$type": "color",
        "$value": "{{color.alpha.white.90}}",
        "$description": "The light rim that makes a surface look lit: fields, chosen pills, cards."
      },
      "subtle": {
        "$type": "color",
        "$value": "{{color.grey.300}}",
        "$description": "Dividers and quiet edges."
      },
      "focus": {
        "$type": "color",
        "$value": "{{color.red.600}}",
        "$description": "Keyboard focus ring on controls."
      },
      "strong": {
        "$type": "color",
        "$value": "{{color.grey.400}}",
        "$description": "Stronger edges and separators."
      }
    },
    "surface": {
      "overlay": {
        "$type": "color",
        "$value": "{{color.alpha.white.100}}",
        "$description": "Floating panels opened from a control: the dropdown and select list, the combobox list, the date picker and the popover. One step above surface/raised, so a menu opened in a modal, a drawer or on a card stands apart. Dialogs (modal, drawer, command palette) stay on surface/default. Not see-through (that is surface/translucent)."
      }
    },
    "text": {
      "default": {
        "$type": "color",
        "$value": "{{color.grey.900}}",
        "$description": "Main text."
      },
      "subtle": {
        "$type": "color",
        "$value": "{{color.grey.700}}",
        "$description": "Secondary text: descriptions, metadata."
      },
      "subtlest": {
        "$type": "color",
        "$value": "{{color.grey.600}}",
        "$description": "Quietest text: hints, captions, placeholders."
      }
    }
  },
  "radius": {
    "container": {
      "sm": {
        "$type": "dimension",
        "$value": "{{radius.8}}",
        "$description": "Corners of cards, panels and dialogs, small."
      }
    }
  },
  "elevation": {
    "overlay": {
      "$type": "shadow",
      "$value": {
        "light": "0px 12px 16px 2px #373d431a, 0px 2px 8px 0px #373d4326",
        "dark": "0px 12px 16px 2px #00000026, 0px 2px 8px 0px #00000033"
      }
    }
  },
  "typography": {
    "label-lg": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "16px",
        "fontWeight": 500,
        "lineHeight": 1.4,
        "letterSpacing": "0px"
      },
      "$description": "Label LG: labels on controls and short UI text (buttons, chips, tabs, menu headings). Geist Medium, sentence case. Code and keys use Code SM and Code MD."
    },
    "body-sm": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "14px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    },
    "body-xs": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "12px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    }
  },
  "layer": {
    "overlay": {
      "$type": "number",
      "$value": 1200
    }
  },
  "focus": {
    "ring": {
      "width": {
        "$type": "dimension",
        "$value": "{{border-width.2}}",
        "$description": "Thickness of the focus ring."
      }
    }
  }
}
```

## Purpose

A small raised panel that opens from a button and stays beside it, for a little more than a tooltip can hold: a title, a few lines, a field, and one or two actions. The page behind it stays usable. It differs from the tooltip, which holds a few words and nothing to press; from the dropdown, which is a list of actions or links; and from the modal, which stops everything else until it is answered.

## Anatomy

- The anchor part, present when the popover draws its own opener: it holds the opener and the layer part, and the layer holds the panel, which hangs from the anchor until it is placed.
- The opener, supplied by the consumer: a button, or a piece of text made a press with the [link](link.md)'s `as="button"`. The popover tells it whether the panel is open and which panel it controls. An opener the popover does not draw is named by its id, and the popover draws no anchor part and leaves that button as it is.
- The block, the raised panel at the popover width, in three sections spaced by the large stack step.
- An optional pointer part, a small triangle on the edge facing the opener that points at the opener's middle. With it, what the panel holds sits in the scroll part, which scrolls in the panel's place, since a panel that scrolled would cut the pointer off.
- The header part: the heading part (an optional icon part, the titles part with the title part and an optional description part) and an optional divider part under it. An optional close part, the small ghost icon button with its cross in the subtlest text color, sits centred on the title's first line, over the padding at the panel's end.
- The body part: a few lines in the description's look, or a field, or both.
- The footer part: a divider part, on unless turned off, then the actions at the end (one button, or a button group for several) or the helper part, a line of small text.

## When to use

- A short explanation with a way to read more, opened from an info button.
- A quick change that belongs to one thing on the page: invite someone, rename, pick a setting.
- A short review with two answers, such as approve and decline.
- A card that previews what a name or a tag points at, such as a person or a place, with opening on hover turned on.

## When not to use

- A few words naming a button. Use the [tooltip](tooltip.md).
- A list of actions or links. Use the [dropdown](dropdown.md).
- A task that must be finished or canceled before anything else. Use the [modal](modal.md).
- A whole form or long reading. Use a page or a [drawer](drawer.md); one click outside closes a popover.
- Opening on hover, for a card that holds the only way to an action. The pointer leaving closes it, so leave the switch off and let a press open it.

## Behavior

Pressing the opener shows the panel beside it, on the side the side axis prefers: below unless it says above, left or right. It lines up with the opener's start or end edge, or its top or foot on the left and right. Pressing it again, pressing Escape, pressing the close button, clicking outside or moving focus outside closes it. Only what is open on top closes: with a dropdown open inside the popover, Escape or a click outside closes the dropdown first, and a popover inside a modal closes before the modal. The panel is lifted over the page while it shows, so no scrolling or clipped part around the opener cuts it off. Without room on the side it prefers, as at the foot of the window, it opens on the opposite side, and preferring the left or the right with room on neither, as on a phone, it opens below; near the window's edge it moves inward; it is never taller than the room on its side, and scrolls inside itself when its content is taller. It stays beside the opener as the page scrolls. An opener inside a part of the page that goes full screen keeps its panel shown over that part.
The panel comes out of the opener: it drops across the gap, growing from its edge nearest the opener, and fades in with the enter purpose, rising and growing upward instead when it opened above and sliding out sideways when it opened at the left or the right, and goes back the same way, toward the opener, with the exit purpose; when the reader's system asks for less motion it fades in and out in place, without traveling or growing.
Without script it hangs under the opener, over it on the above side, or beside it on the left and right sides, under it in a narrow space. A closed panel is hidden, not removed, so anything typed into a field inside it is still there when it opens again.

With `pointer`, a switch that is off unless a page turns it on, the panel points at the middle of its opener, on whichever side it opened, and keeps pointing there when it has moved inward at the window's edge. The pointer stays clear of the panel's corners, so at a very small opener it points a little past the middle. It stands a little further from the opener, by the pointer's reach.

With `openOnHover`, a switch that is off unless a page turns it on, the panel also opens once a mouse has rested on the opener for as long as a tooltip waits, so a pointer passing over opens nothing. It stays while the pointer moves from the opener into the panel, closes a moment after the pointer has left both, and stays open if the pointer comes back within that moment. Opened this way it leaves focus where it is, and Escape closes it without the pointer moving. Pressing the opener then keeps it open as a press would have opened it: focus moves in, and the pointer leaving no longer closes it; moving focus into the panel does the same. A press, the keyboard and touch open it just as they do without the switch.

Without an opener, the panel draws itself open in place, and opening, closing and placing it belong to whoever shows it; its close button calls the change handler.

The opener is given as `trigger`. It can be any element that renders one button and passes the id, aria attributes and click and key handlers it is given on to that button, as the dropdown's opener does; the design system's Button, Tag and a plain button all do. The full interface is written out at the top of the React wrapper.

A piece of text can be the opener too, such as a name that opens its card: give `trigger` a [link](link.md) with `as="button"`. The words keep the look of the text around them and stay in their line, breaking with it. A panel cannot stand inside a paragraph, so a name in a `p` element is named with `openerId` and the popover goes after the paragraph.

An opener the popover cannot draw, such as the button a table row holds, is named instead: pass its id as `openerId`, in place of `trigger`. The popover then draws only its panel, where the page puts it, and the panel hangs on that button. The page holds `open` and sets it from the button's own press, which can simply turn it over: a press on that button is no press outside, so it closes what it opened and never opens it again. Focus moves in as the panel opens, and Escape and the close button give it back to that button, as with `trigger`. To hang the panel on something else, such as the name in a row whose button runs the width of the table, give that element's id as `hangOnId`; it takes no press and no focus. A copy of the button that takes presses for it counts as the button when it names the button's id in `data-copy-of`, as the table's held cell does by itself. Opening on hover takes a `trigger`, since the pointer rests on what the popover draws.

## Accessibility

### What it does for you

- The panel is a non-modal dialog, named by its header's title, or by its label when it has no header.
- An opener given as `trigger` says it opens a dialog, whether it is open, and which panel it controls.
- Opening moves focus into the panel, so a screen reader announces it: to its first field when it holds one, otherwise to the panel itself, named by its title, which draws no ring of its own. Tab goes on through the panel. A popover open as the page loads takes no focus.
- Escape, from the opener or anywhere in the panel, closes it and returns focus to the opener, as the close button does, an opener named with `openerId` included. A click outside closes it without moving focus.
- Nothing in it is reachable only by hover: with opening on hover turned on, a press and the keyboard still open it.
- The pointer is a picture only, hidden from screen readers.
- A panel a resting pointer opened stays while the pointer is on it, and closes with Escape without the pointer moving.
- Every word it says by itself, such as the close button's name, keeps its English unless the page gives its own.

### What you must do

- Give it a close button whenever it holds a field or actions, so touch users have a clear way out. It is named "Close".
- Give the popover a title in its header, or a label when it has no header, so the dialog has a name.
- Give the close button's name in the page's own language when the page is not in English.
- With `openerId`, the popover writes nothing on the button it does not draw: give that button `aria-haspopup="dialog"`, and say while the panel is open, as a table row's `current` does.

### How to test

- Press Enter on the opener: the panel opens and focus moves into it; a screen reader says its title. Press Tab: focus moves through the panel.
- Press Escape inside the panel: it closes and focus returns to the opener.
- With a screen reader, the opener is read as a button that opens a dialog, open or closed, and the panel as a dialog by its title.
- With text at 200% and the page 320 pixels wide, the panel stays inside the window and scrolls inside itself when its content is taller.
- In a forced-color mode, the focus rings on the opener and the close button stay visible, and so does the pointer.
- On a touch screen, the close button closes it.
- With opening on hover turned on, rest the pointer on the opener: the panel opens and focus stays where it was. Move the pointer onto the panel: it stays. Press Escape: it closes.
- With `openerId`, press Enter on the button named: the panel opens and focus moves into it. Press Escape: it closes and focus is back on that button. Press the button twice with the pointer: the second press closes the panel and it stays closed.

### Keyboard

| Key | What it does |
|---|---|
| Enter or Space | On the opener, opens the panel and moves focus into it, or closes it. |
| Tab | With the panel open, moves from the opener into the panel, then through what it holds. |
| Escape | Closes the panel and returns focus to the opener. With a dropdown open inside it, closes the dropdown first. |

## Usage rules

### Do

- Keep it short: a title, a few lines, one or two actions.
- Give it a close button when it holds a field or actions.
- Align a popover at the end of a bar to the end, so it opens inward.
- Prefer the side above for an opener at the foot of a page or a panel.
- Prefer the left or the right where a panel under the opener would cover what a reader is comparing it with, such as the next row or the next item in a graph.
- Give it a pointer when openers sit close together, so it is clear which one the panel belongs to.
- Turn on opening on hover for a card that previews what its opener names, and keep what it says to something a reader can take in where they rest.
- Name an opener with `openerId` only where the popover cannot draw it, such as a table row's own button or words in a paragraph. A second button beside it would be two Tab stops with one job.
- Put a popover with `openerId` soon after its button on the page, such as in a cell of its row, so Tab goes on through the panel from where the reader was.

### Don't

- Open it on hover alone. A press and the keyboard always open it too, and the opener stays a button.
- Put anything in it that the reader must not lose to a stray click outside.
- Use it for a site's navigation.
