Sakuga Back to Sakuga
Theme
Sound
Jump to a component

Core · Containers

Popover

Popover is a raised surface that floats over the page: a menu, a hover card, a small form.

@anicanon/sakuga/uiv1.0Used onWeb

Live, from @anicanon/sakuga 2.0.0

Sakuga for iOS and Android is coming

iOS

Web only

Popover is made for the web, and isn’t part of Sakuga for iOS yet.

Android

Web only

Popover is made for the web, and isn’t part of Sakuga for Android yet.

Use it for
A raised surface anchored to what opened it: a menu, a hover card on a mention, a small form.
Not for
  • A list of options with searchPicker
  • A task that needs the page's whole attentionDialog
  • The same choices on a phoneSheet

Guidelines

  • anchored (default) pins it under its field: wrap the field in a relative container and put the Popover inside. Pass anchored={false} and a style with a position to place it yourself, as for a hover card on a mention.
  • Content is yours. For a list of choices use Picker, which is a Popover with search and options.
  • One Popover open at a time. Escape, a click outside and Tab out of it close it.
  • With sound on, it plays the open sound as it arrives and the close sound as it leaves (README · Sound).
  • It drops from its field on motion-fast with ease-enter. open={false} plays its exit (motion-instant, ease-exit) and then removes it.
  • onClose: with it the popover takes focus while open, Escape or a press outside runs it, and focus goes back to what opened it. Without it the popover never moves focus (a hover card).
  • onKeyDown gets the keys pressed inside it, before its own Escape.
PopoverExample.jsxReact
import { useState } from 'react';
import { Button, Popover } from '@anicanon/sakuga/ui';

export default function PopoverExample() {
  const [open, setOpen] = useState(true);
  return (
    <>
      <Button
        variant="secondary"
        size="sm"
        aria-expanded={open ? 'true' : 'false'}
        onClick={() => setOpen(!open)}
      >
        The Professor
      </Button>
      <Popover
        open={open}
        label="The Professor"
        style={{ gap: 10, padding: 14 }}
      >
        <b>The Professor</b>
        <span style={{ color: 'var(--text-secondary)', fontSize: 13 }}>
          Held in London since Arthur taken.
        </span>
        <Button variant="ghost" size="sm" icon="bookmark">
          Open in Story
        </Button>
      </Popover>
    </>
  );
}

PropsPopoverProps

open
booleanDefault true.
false plays its exit, then it unmounts.
onKeyDown
KeyboardEventHandler
Keys pressed inside it, before its own Escape.
onClose
() => void
With it, the popover takes focus while open; Escape or a press outside runs it, and focus goes back.
anchored
boolean
align
'start' | 'end'
Anchored with its own width, lined up with the anchor's start or end edge (default: the anchor's width).
label
string
role
string
style
CSSProperties
children
ReactNode
className
string
PopoverExample.swiftiOS · coming soon
PopoverExample.ktAndroid · coming soon

Each token reads a role, and the role lands on a value in each theme. Change the role and every component that reads it follows.

Colour

popover-bgPopover and Picker: surface.
bg-surface-raisedneutral-100#ececf0
bg-surface-raisedneutral-875#1a1a21
popover-borderPopover and Picker: outline.
border-dividershade-a12rgba(11,10,16,0.12)
border-dividerwhite-a12rgba(255,255,255,0.12)

Space and shape

popover-gap
gap-xs
4px
popover-padding
inset-sm
8px
popover-radius
radius-md
12px
popover-ring-width
line-hairline
1px
popover-aligned-max-width
its own value
calc(100vw - 2 * 16px)
popover-anchored-top
inset-xs
4px
popover-forced-edge-width
line-hairline
1px

Motion

popover-drop-in-animation
motion-drop-in
sk-drop-in 160ms cubic-bezier(0, 0, 0, 1)
popover-leaving-drop-out-animation
motion-drop-out
sk-drop-out 100ms cubic-bezier(0.3, 0, 1, 1)

Checked automatically with axe-core 4.11.4 against WCAG 2.2 A and AA, on the live preview above, in light and dark, on 26 October 2026.

Result
No issues found, in light or in dark.
By design
A dialog named by label (role changes the role). With onClose it takes focus while open (its first control, else itself), and a press on its words keeps focus inside; Escape, a press outside or Tab out of it runs onClose, and after Escape focus goes back to what opened it; without onClose it never moves focus.
Not tested
The automated check doesn't test keyboard use, focus order or what a screen reader says. The lines above are the design system's own account of them.