Skip to content

HabPopover ​

A sheet of arbitrary content anchored to a trigger — filter forms, color pickers, detail peeks, inline confirms.

A sheet of anything — here a filter form, with a footer
vue
<HabPopover title="Filter sessions" close-button>
	<template #trigger>
		<HabButton
			variant="outlined"
			tone="neutral"
			>Filters</HabButton
		>
	</template>

	<HabCheckbox label="Errors" />

	<template #footer>
		<HabButton size="sm">Apply</HabButton>
	</template>
</HabPopover>

Which anchored sheet ​

Four things in this system anchor a sheet to a trigger, and they differ by what the sheet holds:

ComponentContent
HabTooltipa label, nothing interactive
HabMenua list of actions
HabSelecta list of options
HabPopoveranything that is none of those

HabModal is the escalation. A popover does not trap focus, does not scrim, and dismisses on a stray click — so it must never hold the only copy of unsaved work, or a destructive confirmation.

Placement is a preference ​

placement is a preference — it flips when the requested side is tighter

The sheet flips to the opposite side when the requested one has less room, and clamps into the viewport. Set :flip="false" to pin it exactly, even off-screen.

The resolved position lands on the sheet as data-side and data-align, so a flip is observable rather than silent — useful for pointing an arrow, or for a test.

The sheet is portalled to <body>, which is not incidental: an absolutely positioned sheet is clipped by any overflow: hidden ancestor, and bordered lists, cards, panels and table cells all clip.

Padding ​

none | sm | md | lg, on the 4px grid. none is the one that matters — set it when the child should reach the edges, like a list or a picker grid.

The design contract's default is a free-form 14px, which is not on the grid. This rounds down to 12, the way the type ladder rounds fractional sizes down.

Dismissing ​

Closes on outside click and Esc, both from the primitive.

dismissOnClickInside closes on any click inside, for a sheet that is a one-shot picker. Never set it on a sheet holding a form — a stray click would discard what the user typed.

It listens for a click, anywhere in the content — the padding and the gaps between items included — and not for a key. A picker whose choices are only part of the sheet should close on its own commit instead, holding open itself; HabColorPicker does.

Accessibility ​

  • The sheet is a role="dialog". Give it a title when its contents need naming; that becomes the accessible name.
  • Focus moves into the sheet on open and returns to the trigger on close, from the primitive. It does not trap — Tab leaves, which is correct for a popover and wrong for a modal.
  • The close affordance carries aria-label, from close-label. There is no default — the string is yours to supply, and close-button without it warns.
  • The trigger slot is rendered as the anchor rather than wrapped, so your own button keeps its accessible name.
  • Known limitation: with the sheet open, axe reports aria-hidden-focus against reka's own focus sentinels. Primitive markup, not ours — see design-source.md.

API ​

Props ​

PropTypeDefaultDescription
closeButtonbooleanfalseAdds a close affordance to the header.
closeLabelstringundefinedAccessible name for that close affordance. Required whenever closeButton is set: Habitus ships no default, so the string is the host application's to translate.
disabledbooleanfalseBlocks the trigger and refuses to open. It belongs on the popover rather than on the caller's own trigger markup: the trigger is a slot, so a caller who passes their own cannot stop the sheet opening by disabling it.
dismissOnClickInsidebooleanfalseCloses on any click inside, for a sheet that is a one-shot picker. Never set it on a sheet holding a form: a stray click would discard what the user typed.
flipbooleantrueSet false to pin placement exactly, even off-screen.
maxHeightstring"340px"The sheet scrolls past this — any CSS length.
openbooleanundefinedControlled open state. Omit for self-managed.
padding"md" | "sm" | "lg" | "none""md"—
placement"bottom-start" | "bottom" | "bottom-end" | "top" | "top-start" | "top-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end""bottom-start"—
titlestringundefinedOptional header row. Add it when the sheet's contents need naming.
widthstring"280px"Sheet width — any CSS length.

Events ​

EventPayloadDescription
update:openopen: boolean—

Slots ​

SlotBindingsDescription
defaultanyThe sheet's content.
footeranySticky action row at the bottom — usually Cancel then Apply.
triggeranyThe anchor — a button, a chip, anything clickable.

Proprietary — internal use only.