Skip to content
kitn AI/UI

Button and popover menu

A trigger that opens a floating card — a model picker, a settings menu, an account dropdown. <kai-popover> handles the open/close, positioning, and dismissal; you slot the trigger and whatever content the card needs. The menu below rebuilds the ChatGPT header popover: a flagship model row, an expandable group, and a toggle.

<kai-popover> takes two slots — trigger for the control, and the default slot for the panel. Clicking the trigger toggles the panel; Escape or a click outside closes it (clicks inside don’t). The panel is a role="dialog" region, not a menu, so it holds any markup — rows, a nested group, a toggle switch.

<kai-popover placement="bottom-start">
<button slot="trigger">GPT-5.5 ▾</button>
<div>
<button class="row">GPT-5.5 — Flagship model</button>
<button class="row">Legacy models ▾</button>
<label class="row">Temporary chat <span class="switch"></span></label>
</div>
</kai-popover>
<script type="module">
import '@kitn.ai/ui/elements';
// optional: react to open/close
document.querySelector('kai-popover')
.addEventListener('kai-open-change', (e) => console.log('open:', e.detail.open));
</script>

The panel content lives in your light DOM, so your own styles apply to it directly — no shadow-DOM piercing. kai-popover only frames and positions the card.

By default the popover manages its own open state. To drive it from your app, set the open property and update it from kai-open-change:

const pop = document.querySelector('kai-popover');
pop.open = true; // open it programmatically
pop.addEventListener('kai-open-change', (e) => { pop.open = e.detail.open; });
NameTypeNotes
placementPlacementFloating placement (bottom-start, top, right, …). Defaults to bottom-start.
gutternumberGap in px between trigger and panel. Defaults to 6.
openbooleanControlled open state. Omit for click-to-toggle.
kai-open-changeeventFires { open } on every open/close (click, Escape, outside-click).