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.
Basic use
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 / payload | Callback |
|---|---|
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.