Pager
Navigate pages of records.
Try it
Try minimal and numeric pagination, including the 10, 25, and 50 item page sizes used in Luma Studio. Both pagers share the same current page. This preview runs the exact code sample shown on this page.
Basic use
use gpui::{Context, Entity, Render, Window, div, prelude::*, px};
use xgpui_luma_look_radix as radix;
use xgpui_luma::controls::pager::{Pager, PagerStyle};
use xgpui_luma::controls::selector::{Selector, SelectorItem};
struct Example {
control: Pager,
numeric: Pager,
page_size_select: Entity<Selector>,
page: usize,
page_size: usize,
}
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 mut make_pager = |id, style| {
radix::pager_builder(&look, id)
.style(style).page_count(12).current_page(0)
.page_size(10).page_size_options([10, 25, 50])
.numeric_slot_count(5)
.on_page_changed(cx, |app: &mut Self, page, cx| {
app.page = page;
app.sync(cx);
})
.on_page_size_changed(cx, |app: &mut Self, size, cx| {
app.page_size = size;
app.page = app.page.min(app.page_count() - 1);
app.sync(cx);
})
.spawn(cx)
};
let control = make_pager("minimal", PagerStyle::Minimal);
let numeric = make_pager("numeric", PagerStyle::Numeric);
let page_size_select = radix::Selector::new("page-size").label("Items per page")
.items([
SelectorItem::new("10").label("10 per page"),
SelectorItem::new("25").label("25 per page"),
SelectorItem::new("50").label("50 per page"),
])
.selected_id("10")
.on_value_changed(cx, |app: &mut Self, id, _, cx| {
app.page_size = match id.as_ref() { "25" => 25, "50" => 50, _ => 10 };
app.page = app.page.min(app.page_count() - 1);
app.sync(cx);
})
.spawn(cx);
Self { control, numeric, page_size_select, page: 0, page_size: 10 }
}
fn page_count(&self) -> usize {
120_usize.div_ceil(self.page_size)
}
fn sync(&mut self, cx: &mut Context<Self>) {
for pager in [&self.control, &self.numeric] {
pager.update(cx, |pager, cx| {
pager.set_page_count(self.page_count(), cx);
pager.set_page_size(self.page_size, cx);
pager.set_page(self.page, cx);
});
}
cx.notify();
}
}
impl Render for Example {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div().w_full().max_w(px(600.0)).flex().flex_col().gap_4()
.child(div().w(px(180.0)).child(self.page_size_select.clone()))
.child("Minimal")
.child(self.control.clone())
.child("Numeric")
.child(self.numeric.clone())
.child(format!("Page {} of {} · {} items per page · 120 items", self.page + 1, self.page_count(), self.page_size))
}
}
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.
Radix styling
pager_builder(&look, id) supplies Radix pagination chrome and shared button templates for Minimal, MinimalEdge, and Numeric layouts. SDK page and page-size callbacks retain their existing contracts.
Pass an explicit Look, including forks. Theme adapters retain that binding; notify affected controls and their owners after palette, mode, or stylesheet edits. Resolved look values are snapshots and must be resolved again during render.
See the Radix control support table for adapter and utility status.
SDK behavior & customization
PageChanged and PageSizeChanged coordinate application paging. Keep row data and paging state in sync.
Relevant contracts: PagerTemplateParameters, PagerRenderModel, PagerTemplateHandlers, PagerTemplateModifier.
Use the module’s template contract for structural changes. Builder modifiers and item templates vary by control.
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 |
|---|---|
PageChanged { page: usize } | .on_page_changed(cx, |app, page, cx| { … }) |
PageSizeChanged { page_size: usize } | .on_page_size_changed(cx, |app, page_size, cx| { … }) |
PageSizeOpenChanged { open: bool } | .on_page_size_open_changed(cx, |app, open, cx| { … }) |
EnabledChanged { enabled: bool } | .on_enabled_changed(cx, |app, enabled, cx| { … }) |
Use .on_event(cx, |app, event, cx| { … }) for all events. Factory-created controls support the same callbacks through PagerEventExt. Existing cx.subscribe observers remain supported.