Skip to content
kerfjsGitHub

Popover

A small panel that opens from a button and stays out of the way.

SourcePackage @kerf-js/veneerTry it

Preview

tsx
import { Popover } from '@kerf-js/veneer'export function Basic() {  return (  <Popover    label="What is a preview?"    title="Preview deploys"    description={      'Every branch gets its own address, so' +      ' you can try a change before it goes live.'    }  />  )}

Installation

npm install @kerf-js/veneer

Try it

Change the controls: the preview and its code update together. The address keeps what you set, so you can link to it.

Live preview
Controls
Side
Align
Codeupdates as you change the controls
import { Popover } from '@kerf-js/veneer'<Popover label="Details" title="storefront" description="Deployed 2 minutes ago by Priya." />

Usage

tsx
import { Popover } from '@kerf-js/veneer'<Popover  label="What is a preview?"  title="Preview deploys"  description={    'Every branch gets its own address, so' +    ' you can try a change before it goes live.'  }/>

Placement

Use side to pick where it opens. With no room there, it flips.

tsx
import { Popover } from '@kerf-js/veneer'export function Sides() {  return (  <>    <Popover      label="Top"      side="top"      title="Top"      description="Opens above the button."    />    <Popover      label="Bottom"      side="bottom"      title="Bottom"      description="Opens below the button."    />    <Popover      label="Left"      side="left"      title="Left"      description="Opens before the button."    />    <Popover      label="Right"      side="right"      title="Right"      description="Opens after the button."    />  </>  )}

Alignment

Use align to line it up with the button.

tsx
import { Popover } from '@kerf-js/veneer'export function Aligns() {  return (  <>    <Popover      label="Start"      align="start"      title="Start"      description={        'Lines up with the left edge. The default.'      }    />    <Popover      label="Center"      align="center"      title="Center"      description="Sits in the middle."    />    <Popover      label="End"      align="end"      title="End"      description="Lines up with the right edge."    />  </>  )}

Trigger styles

trigger takes any Button prop.

tsx
import { Popover } from '@kerf-js/veneer'export function Trigger() {  return (  <>    <Popover      label="Soft"      trigger={{ emphasis: 'soft' }}      title="Soft"    />    <Popover      label="With an icon"      trigger={{        icon: 'info',        emphasis: 'ghost',        tone: 'neutral',      }}      title="Ghost"    />    <Popover      label="Small"      trigger={{ size: 'sm' }}      title="Small"    />  </>  )}

Closing from inside

Anything with data-close-popover closes it.

tsx
import { state } from '@kerf-js/core'import {  Button,  Popover,  Row,  Stack,  Text} from '@kerf-js/veneer'export function Close() {  const on = state('Nobody is on call')  return (    <Stack      gap="related"      align="center"    >      <Text subtle>{on.value}</Text>      <Popover        label="Take the pager"        title="Go on call?"        description={          'Alerts for every project' +          ' will come to you tonight.'        }      >        <Row          gap="related"          justify="end"        >          <Button            size="sm"            emphasis="ghost"            tone="neutral"            data-close-popover=""          >            Not now          </Button>          <Button            size="sm"            data-close-popover=""            onClick={() => (on.value = 'You are on call')}          >            Go on call          </Button>        </Row>      </Popover>    </Stack>  )}

A filter

A popover opens a small panel from its trigger, here a set of filter chips.

tsx
import { state } from '@kerf-js/core'import { Chip, Popover, Row, Text } from '@kerf-js/veneer'export function Filter() {  const on = state(['Failed'])  const flip = (s: string) =>    (on.value = on.value.includes(s)      ? on.value.filter((x) => x !== s)      : [...on.value, s])  return (    <Row gap="related">      <Popover        label={`Status (${on.value.length})`}        trigger={{ icon: 'filter' }}        title="Show deploys that are"      >        <Row gap="tight">          {STATUSES.map((s) => (            <Chip              key={s}              pressed={on.value.includes(s)}              onClick={() => flip(s)}            >              {s}            </Chip>          ))}        </Row>      </Popover>      <Text subtle>        {on.value.join(', ') || 'Everything'}      </Text>    </Row>  )}const STATUSES = ['Live', 'Building', 'Failed']

When to use

Use it for

  • A few controls or a short note, next to what they affect
  • People may carry on with the page while it is open

Reach for something else

  • Tooltip, when it is a hint with nothing to press
  • Menu, when it is a list of actions
  • Modal, when it must be answered first

Accessibility

Enter
Opens it and moves focus inside
Esc
Closes and returns focus to the button
Tab
Leaving the panel closes it
  • It is a non-modal dialog, named by its title, else by its button.
  • The button reports whether it is open.

Theming

Restyle it app-wide from veneer() in your kerf config. Every key takes colour, spacing, size, radius, shadow, type and motion; its own keys are listed. A state nests inside: hover: { bg: 'ink' }. Text on a fill you set is contrast-checked at build. How theming works

KeyOwn keysStates
parts.popover—none
ts
veneer({  colors: { brand: '#e8590c', ink: '#1b1b1f' },  parts: {    popover: { radius: 12, shadow: 'lg' },  },})

API Reference

PropTypeDefaultDescription
labelunknown—The button's text.
titleunknown—A heading in the panel. Also names it.
descriptionunknown—A line under the title.
sideSide—Which side of the button it opens on.
alignAlign—How it lines up with the button.
triggerButtonProps—Button props for the trigger.
inlinebooleanfalseDraws the panel open under its trigger, in the page's flow: no top layer or light dismiss. The trigger still shows and hides it. For docs and previews.
childrenunknown—What it contains.

Parts

Every element carries a stable name, so you can style it from your own CSS: [data-scope="popover"] [data-part="content"]

contenttitledescription

RelatedTooltipMenuModal