Scrollable
A comprehensive scrollable container component that provides custom scrollbars, scroll tracking, and virtualization capabilities. Supports both vertical and horizontal scrolling with customizable appearance and behavior.
#Import
use gpui_kit::component::{
scroll::{ScrollableElement, ScrollbarAxis, ScrollbarMode},
StyledExt as _,
};
#Usage
#Basic Scrollable Container
The simplest way to make any element scrollable is using the overflow_scrollbar() method from ScrollableElement trait.
This method is almost like the overflow_scroll() method, but it adds scrollbars.
overflow_scrollbar()- Adds scrollbars for both axes as needed.overflow_x_scrollbar()- Adds horizontal scrollbar as needed.overflow_y_scrollbar()- Adds vertical scrollbar as needed.
use gpui_kit::{div, Axis};
use gpui_kit::component::ScrollableElement;
div()
.id("scrollable-container")
.size_full()
.child("Your content here")
.overflow_scrollbar()
#Vertical Scrolling
v_flex()
.id("scrollable-container")
.overflow_y_scrollbar()
.gap_2()
.p_4()
.child("Scrollable Content")
.children((0..100).map(|i| {
div()
.h(px(40.))
.w_full()
.bg(cx.theme().secondary)
.child(format!("Item {}", i))
}))
#Horizontal Scrolling
h_flex()
.id("scrollable-container")
.overflow_x_scrollbar()
.gap_2()
.p_4()
.children((0..50).map(|i| {
div()
.min_w(px(120.))
.h(px(80.))
.bg(cx.theme().accent)
.child(format!("Card {}", i))
}))
#Both Directions
div()
.id("scrollable-container")
.size_full()
.overflow_scrollbar()
.child(
div()
.w(px(2000.)) // Wide content
.h(px(2000.)) // Tall content
.bg(cx.theme().background)
.child("Large content area")
)
#Custom Scrollbars
#Manual Scrollbar Creation
For more control, you can create scrollbars manually:
use gpui_kit::component::scroll::{ScrollableElement};
pub struct ScrollableView {
scroll_handle: ScrollHandle,
}
impl Render for ScrollableView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.relative()
.size_full()
.child(
div()
.id("content")
.track_scroll(&self.scroll_handle)
.overflow_scroll()
.size_full()
.child("Your scrollable content")
)
.vertical_scrollbar(&self.scroll_handle)
}
}
#Virtualization
#VirtualList for Large Datasets
For rendering large lists efficiently, use VirtualList:
use gpui_kit::component::{VirtualList, VirtualListScrollHandle};
pub struct LargeListView {
items: Vec<String>,
scroll_handle: VirtualListScrollHandle,
}
impl Render for LargeListView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let item_count = self.items.len();
VirtualList::new(
self.scroll_handle.clone(),
item_count,
|ix, window, cx| {
// Item sizes - can be different for each item
size(px(300.), px(40.))
},
|ix, bounds, selected, window, cx| {
// Render each item
div()
.size(bounds.size)
.bg(if selected {
cx.theme().accent
} else {
cx.theme().background
})
.child(format!("Item {}: {}", ix, self.items[ix]))
.into_any_element()
},
)
}
}
#Scrolling to Specific Items
impl LargeListView {
fn scroll_to_item(&mut self, index: usize) {
self.scroll_handle.scroll_to_item(index, ScrollStrategy::Top);
}
fn scroll_to_item_centered(&mut self, index: usize) {
self.scroll_handle.scroll_to_item(index, ScrollStrategy::Center);
}
}
#Variable Item Sizes
VirtualList::new(
scroll_handle,
items.len(),
|ix, window, cx| {
// Different heights based on content
let height = if items[ix].len() > 50 {
px(80.) // Tall items for long content
} else {
px(40.) // Normal height
};
size(px(300.), height)
},
|ix, bounds, selected, window, cx| {
// Render logic
},
)
#Theme Customization
#Scrollbar Appearance
Customize scrollbar appearance through theme configuration:
// In your theme JSON
{
"scrollbar.background": "#ffffff20",
"scrollbar.thumb.background": "#00000060",
"scrollbar.thumb.hover.background": "#000000"
}
#Scrollbar Show Modes
Control when scrollbars are visible:
use gpui_kit::component::{Theme, scroll::ScrollbarMode};
// In theme initialization
Theme::set_scrollbar_mode(ScrollbarMode::Scrolling, cx); // Only while scrolling
Theme::set_scrollbar_mode(ScrollbarMode::Hover, cx); // On hover
Theme::set_scrollbar_mode(ScrollbarMode::Always, cx); // Always visible
#System Integration
Sync scrollbar behavior with system preferences:
// Automatically sync with system settings
Theme::sync_scrollbar_appearance(cx);
#Examples
#File Browser with Scrolling
pub struct FileBrowser {
files: Vec<String>,
}
impl Render for FileBrowser {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.border_1()
.border_color(cx.theme().border)
.size_full()
.child(
v_flex()
.gap_1()
.p_2()
.overflow_y_scrollbar()
.children(self.files.iter().map(|file| {
div()
.h(px(32.))
.w_full()
.px_2()
.flex()
.items_center()
.hover(|style| style.bg(cx.theme().secondary_hover))
.child(file.clone())
}))
)
}
}
#Chat Messages with Auto-scroll
pub struct ChatView {
messages: Vec<String>,
scroll_handle: ScrollHandle,
should_auto_scroll: bool,
}
impl ChatView {
fn add_message(&mut self, message: String) {
self.messages.push(message);
if self.should_auto_scroll {
// Scroll to bottom for new messages
let max_offset = self.scroll_handle.max_offset();
self.scroll_handle.set_offset(point(px(0.), max_offset.y));
}
}
}
#Data Table with Virtual Scrolling
pub struct DataTable {
data: Vec<Vec<String>>,
scroll_handle: VirtualListScrollHandle,
}
impl Render for DataTable {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
VirtualList::new(
self.scroll_handle.clone(),
self.data.len(),
|_ix, _window, _cx| size(px(800.), px(32.)), // Fixed row height
|ix, bounds, _selected, _window, cx| {
h_flex()
.size(bounds.size)
.border_b_1()
.border_color(cx.theme().border)
.children(self.data[ix].iter().map(|cell| {
div()
.flex_1()
.px_2()
.flex()
.items_center()
.child(cell.clone())
}))
.into_any_element()
},
)
}
}
#Scroll bounce
div()
.child(content)
.overflow_y_scrollbar()
.scroll_bounce(true)
scroll_bounce(true) lets overflowing vertical content stretch at its ends and
spring back with precise pixel scrolling, such as a trackpad or Magic Mouse
on macOS. Ordinary mouse wheels use line scrolling and do not bounce. A new
precise gesture or reversed precise scroll takes over the return; ordinary
wheel input cancels it and scrolls normally. The logical scroll position remains clamped,
and scrollbars stay fixed. Content that fits does not bounce. It is enabled by default on iOS, Android and macOS, whose own scroll views bounce, and disabled elsewhere. Reduced motion disables displacement.
Nested scroll areas follow the platform: the innermost one under the pointer takes the whole gesture and bounces at its own ends instead of handing the rest to the area around it. Text inputs, lists, tables and trees take part in the same rule.
For a custom viewport, wrap it in
gpui_kit::base::ScrollBounce::new(id, &handle, child). The wrapper owns the
vertical wheel scrolling and follows the platform default; .enabled(false)
keeps the scrolling and drops the stretch. Keep fixed chrome outside the
wrapper. .scroll_wheel(false) leaves scrolling to the child and only bounces
its unused deltas, for touch input. Horizontal-only Scrollable containers do
not bounce.