Popover Button

A trigger-owned popup surface.

Try it

Click Details to open a styled popover. Click outside the panel to dismiss it. This preview runs the exact code sample shown on this page.

Open example separately ↗

Basic use

SDK · integration snippet
use gpui::{Context, div, prelude::*, px, size};
use xgpui_luma::controls::popover_button::{PopoverButton, PopoverPlacement};
use xgpui_luma::infra::presenter::HasPresenter;
use xgpui_luma_look_radix as radix;

struct Example {
    control: gpui::Entity<PopoverButton>,
    details_open: bool,
}

impl Example {
    fn new(cx: &mut Context<Self>) -> Self {
        let look = cx.try_global::<radix::Look>().cloned().unwrap_or_else(radix::Look::built_in);
        let trigger = radix::Button::new("details-trigger").label("Details")
            .on_click(cx, |app: &mut Self, cx| {
                app.control.update(cx, |popover, cx| popover.toggle_guarded(cx));
            }).spawn(cx);
        let control = PopoverButton::<()>::new("details")
            .trigger(move |_, _| trigger.clone().into_any_element())
            .content(move |_, _, _| {
                div().w(px(260.0)).p_4().flex().flex_col().gap_2()
                    .bg(look.resolve_role(radix::SemanticRole::Surface).hsla())
                    .text_color(look.resolve_role(radix::SemanticRole::Foreground).hsla())
                    .border_1().border_color(look.resolve_role(radix::SemanticRole::Border).hsla())
                    .rounded(px(6.0))
                    .child("Project details")
                    .child("A styled popover attached to a Radix button.")
                    .into_any_element()
            })
            .placement(PopoverPlacement::Smart)
            .initial_content_size(size(px(260.0), px(110.0)))
            .trigger_on_pointer_down(false)
            .on_open_changed(cx, |app, open, cx| {
                app.details_open = open;
                cx.notify();
            })
            .spawn(cx);
        Self { control, details_open: false }
    }
}

Integration snippet, checked against the current SDK and Radix crates. cx is the initialized owner context. The control retains its callbacks; callback owners are held weakly. Render self.control.clone() in the owner’s view.

SDK behavior & customization

Placement and popup lifecycle belong to the control; supply the popup content.

Relevant contracts: PopoverRenderModel.

Events

Callbacks follow the existing SDK event stream. Registrations append, hold the owner weakly, and live until the control is released. cx.notify() requests an owner redraw. Compound payloads are borrowed; scalar values are passed by value.

Event / payloadCallback
OpenChanged { open: bool }.on_open_changed(cx, |app, open, cx| { … })

Use .on_event(cx, |app, event, cx| { … }) for all events. Factory-created controls support the same callbacks through PopoverButtonEventExt. Existing cx.subscribe observers remain supported.

Source

SDK module