Skip to main content

ScrollBar

Struct ScrollBar 

Source
pub struct ScrollBar { /* private fields */ }
Expand description

A proportional scrollbar widget with fractional thumb rendering.

§Method map

§Construction

§Position and lengths

§Appearance

§Interaction

§Important

  • content_len and viewport_len are in logical units.
  • Zero values are treated as 1.
  • The scrollbar renders into a single row or column.

§Behavior

The thumb length is proportional to viewport_len / content_len and clamped to at least one full cell for usability. When content_len <= viewport_len, the thumb fills the track. Areas with zero width or height render nothing.

Arrow endcaps, when enabled, consume one cell at the start/end of the track. The thumb and track render in the remaining inner area. Clicking an arrow steps the offset by scroll_step.

§Styling

Track glyphs use track_style. Thumb glyphs use thumb_style. Arrow endcaps use arrow_style, which defaults to white on dark gray.

Scrollbar glyphs are terminal characters. For visible track glyphs, thumb blocks, and arrow symbols, Style::fg colors the glyph itself and Style::bg colors the cell behind it. The default GlyphSet::minimal track renders spaces, so only the track background is visible in empty track cells. Visible track glyph sets, such as GlyphSet::box_drawing and GlyphSet::unicode, can use foreground color for the track line. Thumb glyphs are block characters, so Style::fg is usually the useful knob for thumb color; Style::bg still colors the rest of the cell. With partial thumb glyphs, especially on a visible line track such as GlyphSet::box_drawing, that background can show at the ends of the thumb. Match the thumb background to the track background unless that contrast is intentional.

use ratatui_core::style::{Color, Style};
use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 30,
};
let scrollbar = ScrollBar::vertical(lengths)
    .arrows(ScrollBarArrows::Both)
    .track_style(Style::new().bg(Color::Black))
    .thumb_style(Style::new().fg(Color::Rgb(255, 158, 100)))
    .arrow_style(Style::new().fg(Color::Yellow).bg(Color::Black));

§State

This widget is stateless. Pointer drag state lives in crate::ScrollBarInteraction.

§Examples

Minimal rendering only needs an area, lengths, an offset, and a buffer.

use ratatui_core::buffer::Buffer;
use ratatui_core::layout::Rect;
use ratatui_core::widgets::Widget;
use tui_scrollbar::{ScrollBar, ScrollLengths};

let area = Rect::new(0, 0, 1, 5);
let lengths = ScrollLengths {
    content_len: 200,
    viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).offset(60);

let mut buffer = Buffer::empty(area);
scrollbar.render(area, &mut buffer);

§Updating offsets on input

This is the typical pattern for pointer handling: feed events to the scrollbar and apply the returned command to your stored offset.

use ratatui_core::layout::Rect;
use tui_scrollbar::{
    PointerButton, PointerEvent, PointerEventKind, ScrollBar, ScrollBarInteraction,
    ScrollCommand, ScrollEvent, ScrollLengths,
};

let area = Rect::new(0, 0, 1, 10);
let lengths = ScrollLengths {
    content_len: 400,
    viewport_len: 80,
};
let scrollbar = ScrollBar::vertical(lengths).offset(0);
let mut interaction = ScrollBarInteraction::new();
let mut offset = 0;

let event = ScrollEvent::Pointer(PointerEvent {
    column: 0,
    row: 3,
    kind: PointerEventKind::Down,
    button: PointerButton::Primary,
});

if let Some(ScrollCommand::SetOffset(next)) =
    scrollbar.handle_event(area, event, &mut interaction)
{
    offset = next;
}

§Track click behavior

Choose between classic page jumps or jump-to-click behavior.

use tui_scrollbar::{ScrollBar, ScrollLengths, TrackClickBehavior};

let lengths = ScrollLengths {
    content_len: 10,
    viewport_len: 5,
};
let scrollbar =
    ScrollBar::vertical(lengths).track_click_behavior(TrackClickBehavior::JumpToClick);

§Arrow endcaps

Arrow endcaps are optional. When enabled, they reserve one cell at each end of the track.

use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 24,
};
let scrollbar = ScrollBar::vertical(lengths).arrows(ScrollBarArrows::Both);

Implementations§

Source§

impl ScrollBar

Source

pub fn handle_event( &self, area: Rect, event: ScrollEvent, interaction: &mut ScrollBarInteraction, ) -> Option<ScrollCommand>

Handles a backend-agnostic scrollbar event.

Returns a ScrollCommand when the event should update the caller-owned offset. This method does not mutate your application state directly.

Pointer events outside the track are ignored. Scroll wheel events are ignored unless the axis matches the scrollbar orientation.

use ratatui_core::layout::Rect;
use tui_scrollbar::{
    PointerButton, PointerEvent, PointerEventKind, ScrollBar, ScrollBarInteraction,
    ScrollEvent, ScrollLengths,
};

let area = Rect::new(0, 0, 1, 6);
let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 24,
};
let scrollbar = ScrollBar::vertical(lengths).offset(0);
let mut interaction = ScrollBarInteraction::new();
let event = ScrollEvent::Pointer(PointerEvent {
    column: 0,
    row: 2,
    kind: PointerEventKind::Down,
    button: PointerButton::Primary,
});

let _ = scrollbar.handle_event(area, event, &mut interaction);
Source

pub fn handle_mouse_event( &self, area: Rect, event: MouseEvent, interaction: &mut ScrollBarInteraction, ) -> Option<ScrollCommand>

Handles crossterm mouse events for this scrollbar.

This helper converts crossterm events into ScrollEvent values before delegating to Self::handle_event. See the scrollbar_mouse example for a complete terminal event loop with mouse capture.

Examples found in repository?
examples/scrollbar_mouse.rs (lines 270-274)
262    fn handle_mouse_event(&mut self, event: event::MouseEvent) {
263        let Some(layout) = self.layout else {
264            return;
265        };
266        let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
267        let horizontal = self.horizontal_scrollbar(h_metrics);
268        let vertical = self.vertical_scrollbar(v_metrics);
269
270        if let Some(command) = horizontal.handle_mouse_event(
271            layout.horizontal_bar,
272            event,
273            &mut self.horizontal_interaction,
274        ) {
275            self.apply_command(command, true);
276        }
277        if let Some(command) =
278            vertical.handle_mouse_event(layout.vertical_bar, event, &mut self.vertical_interaction)
279        {
280            self.apply_command(command, false);
281        }
282    }
Source§

impl ScrollBar

Source

pub fn new(orientation: ScrollBarOrientation, lengths: ScrollLengths) -> Self

Creates a scrollbar with the given orientation and lengths.

Use Self::vertical or Self::horizontal when the orientation is known at the call site.

Zero lengths are treated as 1.

use tui_scrollbar::{ScrollBar, ScrollBarOrientation, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 40,
};
let scrollbar = ScrollBar::new(ScrollBarOrientation::Vertical, lengths);
Source

pub fn vertical(lengths: ScrollLengths) -> Self

Creates a vertical scrollbar with the given content and viewport lengths.

The track length is derived from the render area’s height.

use tui_scrollbar::{ScrollBar, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths);
Examples found in repository?
examples/scrollbar.rs (line 170)
153fn render_vertical_steps(frame: &mut ratatui::Frame, cells: Vec<Rect>) {
154    for (index, area) in cells.iter().enumerate() {
155        let [label_area, bar_area] = area.layout(&Layout::vertical([
156            Constraint::Length(1),
157            Constraint::Fill(1),
158        ]));
159        if bar_area.height == 0 {
160            continue;
161        }
162        let metrics = build_metrics(bar_area.height as usize, 3);
163        let (label, thumb_start) = step_entry(&metrics, index);
164        let label = (label % 8).to_string();
165        let offset = metrics.offset_for_thumb_start(thumb_start);
166        let lengths = ScrollLengths {
167            content_len: metrics.content_len(),
168            viewport_len: metrics.viewport_len(),
169        };
170        let scrollbar = ScrollBar::vertical(lengths)
171            .arrows(ScrollBarArrows::Both)
172            .offset(offset);
173        render_label(frame, label_area, &label);
174        frame.render_widget(&scrollbar, bar_area);
175    }
176}
More examples
Hide additional examples
examples/scrollbar_styled.rs (line 65)
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25    if area.width < 2 || area.height < 2 {
26        return;
27    }
28
29    let horizontal_bar = area
30        .rows()
31        .next_back()
32        .unwrap_or(area)
33        .inner(Margin::new(1, 0));
34    let vertical_bar = area
35        .columns()
36        .next_back()
37        .unwrap_or(area)
38        .inner(Margin::new(0, 1));
39
40    let block = Block::new()
41        .borders(Borders::ALL)
42        .title("styled scrollbars")
43        .border_style((Color::LightBlue, Color::Black))
44        .style((Color::Gray, Color::Black));
45    let content = block.inner(area).inner(Margin::new(2, 1));
46    frame.render_widget(block, area);
47
48    frame.render_widget(
49        Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50            .style((Color::Gray, Color::Black)),
51        content,
52    );
53
54    let lengths = ScrollLengths {
55        content_len: 160,
56        viewport_len: 40,
57    };
58    let horizontal = ScrollBar::horizontal(lengths)
59        .offset(48)
60        .arrows(ScrollBarArrows::Both)
61        .glyph_set(GlyphSet::box_drawing())
62        .track_style((Color::Blue, Color::Black).into())
63        .thumb_style((Color::Yellow, Modifier::BOLD).into())
64        .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65    let vertical = ScrollBar::vertical(lengths)
66        .offset(80)
67        .arrows(ScrollBarArrows::Both)
68        .glyph_set(GlyphSet::box_drawing())
69        .track_style((Color::Magenta, Color::Black).into())
70        .thumb_style((Color::Cyan, Modifier::BOLD).into())
71        .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73    frame.render_widget(&horizontal, horizontal_bar);
74    frame.render_widget(&vertical, vertical_bar);
75}
examples/scrollbar_mouse.rs (line 210)
123    fn render(&mut self, frame: &mut ratatui::Frame) {
124        let area = frame.area();
125        if area.width < 2 || area.height < 2 {
126            return;
127        }
128
129        let title = "tui-scrollbar - mouse scroll demo";
130        let block = Block::new()
131            .borders(Borders::TOP)
132            .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133            .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134            .title(
135                Line::from(title)
136                    .centered()
137                    .fg(TITLE_FG)
138                    .bg(TITLE_BG)
139                    .bold(),
140            );
141        frame.render_widget(&block, area);
142
143        let content_area = Rect {
144            y: area.y.saturating_add(1),
145            height: area.height.saturating_sub(1),
146            ..area
147        };
148        let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149        let help_area = Rect {
150            x: content_area.x.saturating_add(1),
151            y: content_area.y,
152            width: content_area.width.saturating_sub(1),
153            height: 1,
154        };
155        if help_area.width > 0 {
156            frame.render_widget(
157                Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158                help_area,
159            );
160        }
161        let content_area = Rect {
162            y: content_area.y.saturating_add(1),
163            height: content_area.height.saturating_sub(1),
164            ..content_area
165        };
166
167        // Split out the bottom row and right column for the scrollbars.
168        let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169            Constraint::Fill(1),
170            Constraint::Length(1),
171        ]));
172        let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173            Constraint::Fill(1),
174            Constraint::Length(1),
175        ]));
176        let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177            Constraint::Fill(1),
178            Constraint::Length(1),
179        ]));
180
181        self.layout = Some(LayoutState {
182            content,
183            vertical_bar,
184            horizontal_bar,
185        });
186
187        // Keep offsets valid when the terminal is resized.
188        let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189        self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190        self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192        let horizontal_lengths = ScrollLengths {
193            content_len: h_metrics.content_len(),
194            viewport_len: h_metrics.viewport_len(),
195        };
196        let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197        let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198        let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199        let horizontal = ScrollBar::horizontal(horizontal_lengths)
200            .arrows(ScrollBarArrows::Both)
201            .offset(self.horizontal_offset)
202            .scroll_step(SUBCELL)
203            .track_style(track_style)
204            .thumb_style(thumb_style)
205            .arrow_style(arrow_style);
206        let vertical_lengths = ScrollLengths {
207            content_len: v_metrics.content_len(),
208            viewport_len: v_metrics.viewport_len(),
209        };
210        let vertical = ScrollBar::vertical(vertical_lengths)
211            .arrows(ScrollBarArrows::Both)
212            .offset(self.vertical_offset)
213            .scroll_step(SUBCELL)
214            .track_style(track_style)
215            .thumb_style(thumb_style)
216            .arrow_style(arrow_style);
217
218        frame.render_widget(&horizontal, horizontal_bar);
219        frame.render_widget(&vertical, vertical_bar);
220    }
221
222    /// Handles keyboard and mouse events, updating offsets as needed.
223    fn handle_events(&mut self) -> Result<()> {
224        match event::read()? {
225            Event::Key(key) => {
226                if key.is_press() {
227                    self.handle_key_event(key.code);
228                }
229            }
230            Event::Mouse(event) => {
231                self.handle_mouse_event(event);
232            }
233            _ => {}
234        }
235        Ok(())
236    }
237
238    /// Handles keyboard input, updating offsets or exiting as needed.
239    fn handle_key_event(&mut self, code: KeyCode) {
240        match code {
241            KeyCode::Char('q') | KeyCode::Esc => self.state = AppState::Quit,
242            KeyCode::Up | KeyCode::Char('k') => self.handle_key_scroll(0, -(KEY_STEP as isize)),
243            KeyCode::Down | KeyCode::Char('j') => self.handle_key_scroll(0, KEY_STEP as isize),
244            KeyCode::Left | KeyCode::Char('h') => self.handle_key_scroll(-(KEY_STEP as isize), 0),
245            KeyCode::Right | KeyCode::Char('l') => self.handle_key_scroll(KEY_STEP as isize, 0),
246            _ => {}
247        }
248    }
249
250    /// Applies a keyboard delta to the scrollbar offsets.
251    fn handle_key_scroll(&mut self, dx: isize, dy: isize) {
252        let Some(layout) = self.layout else {
253            return;
254        };
255        let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
256        self.horizontal_offset =
257            Self::apply_delta(self.horizontal_offset, dx, h_metrics.max_offset());
258        self.vertical_offset = Self::apply_delta(self.vertical_offset, dy, v_metrics.max_offset());
259    }
260
261    /// Handles crossterm mouse events using the scrollbar helpers.
262    fn handle_mouse_event(&mut self, event: event::MouseEvent) {
263        let Some(layout) = self.layout else {
264            return;
265        };
266        let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
267        let horizontal = self.horizontal_scrollbar(h_metrics);
268        let vertical = self.vertical_scrollbar(v_metrics);
269
270        if let Some(command) = horizontal.handle_mouse_event(
271            layout.horizontal_bar,
272            event,
273            &mut self.horizontal_interaction,
274        ) {
275            self.apply_command(command, true);
276        }
277        if let Some(command) =
278            vertical.handle_mouse_event(layout.vertical_bar, event, &mut self.vertical_interaction)
279        {
280            self.apply_command(command, false);
281        }
282    }
283
284    /// Applies a scroll command to the current axis offset.
285    fn apply_command(&mut self, command: ScrollCommand, is_horizontal: bool) {
286        let ScrollCommand::SetOffset(offset) = command;
287        if is_horizontal {
288            self.horizontal_offset = offset;
289        } else {
290            self.vertical_offset = offset;
291        }
292    }
293
294    /// Builds a horizontal scrollbar from the current metrics.
295    fn horizontal_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
296        let lengths = ScrollLengths {
297            content_len: metrics.content_len(),
298            viewport_len: metrics.viewport_len(),
299        };
300        ScrollBar::horizontal(lengths)
301            .arrows(ScrollBarArrows::Both)
302            .offset(self.horizontal_offset)
303            .scroll_step(SUBCELL)
304    }
305
306    /// Builds a vertical scrollbar from the current metrics.
307    fn vertical_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
308        let lengths = ScrollLengths {
309            content_len: metrics.content_len(),
310            viewport_len: metrics.viewport_len(),
311        };
312        ScrollBar::vertical(lengths)
313            .arrows(ScrollBarArrows::Both)
314            .offset(self.vertical_offset)
315            .scroll_step(SUBCELL)
316    }
Source

pub fn horizontal(lengths: ScrollLengths) -> Self

Creates a horizontal scrollbar with the given content and viewport lengths.

The track length is derived from the render area’s width.

use tui_scrollbar::{ScrollBar, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 40,
};
let scrollbar = ScrollBar::horizontal(lengths);
Examples found in repository?
examples/scrollbar.rs (line 144)
127fn render_horizontal_steps(frame: &mut ratatui::Frame, cells: Vec<Rect>) {
128    for (index, area) in cells.iter().enumerate() {
129        let [label_area, bar_area] = area.layout(&Layout::horizontal([
130            Constraint::Length(2),
131            Constraint::Fill(1),
132        ]));
133        if bar_area.width == 0 {
134            continue;
135        }
136        let metrics = build_metrics(bar_area.width as usize, 6);
137        let (label, thumb_start) = step_entry(&metrics, index);
138        let label = (label % 8).to_string();
139        let offset = metrics.offset_for_thumb_start(thumb_start);
140        let lengths = ScrollLengths {
141            content_len: metrics.content_len(),
142            viewport_len: metrics.viewport_len(),
143        };
144        let scrollbar = ScrollBar::horizontal(lengths)
145            .arrows(ScrollBarArrows::Both)
146            .offset(offset);
147        render_label(frame, label_area, &label);
148        frame.render_widget(&scrollbar, bar_area);
149    }
150}
More examples
Hide additional examples
examples/scrollbar_styled.rs (line 58)
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25    if area.width < 2 || area.height < 2 {
26        return;
27    }
28
29    let horizontal_bar = area
30        .rows()
31        .next_back()
32        .unwrap_or(area)
33        .inner(Margin::new(1, 0));
34    let vertical_bar = area
35        .columns()
36        .next_back()
37        .unwrap_or(area)
38        .inner(Margin::new(0, 1));
39
40    let block = Block::new()
41        .borders(Borders::ALL)
42        .title("styled scrollbars")
43        .border_style((Color::LightBlue, Color::Black))
44        .style((Color::Gray, Color::Black));
45    let content = block.inner(area).inner(Margin::new(2, 1));
46    frame.render_widget(block, area);
47
48    frame.render_widget(
49        Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50            .style((Color::Gray, Color::Black)),
51        content,
52    );
53
54    let lengths = ScrollLengths {
55        content_len: 160,
56        viewport_len: 40,
57    };
58    let horizontal = ScrollBar::horizontal(lengths)
59        .offset(48)
60        .arrows(ScrollBarArrows::Both)
61        .glyph_set(GlyphSet::box_drawing())
62        .track_style((Color::Blue, Color::Black).into())
63        .thumb_style((Color::Yellow, Modifier::BOLD).into())
64        .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65    let vertical = ScrollBar::vertical(lengths)
66        .offset(80)
67        .arrows(ScrollBarArrows::Both)
68        .glyph_set(GlyphSet::box_drawing())
69        .track_style((Color::Magenta, Color::Black).into())
70        .thumb_style((Color::Cyan, Modifier::BOLD).into())
71        .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73    frame.render_widget(&horizontal, horizontal_bar);
74    frame.render_widget(&vertical, vertical_bar);
75}
examples/scrollbar_mouse.rs (line 199)
123    fn render(&mut self, frame: &mut ratatui::Frame) {
124        let area = frame.area();
125        if area.width < 2 || area.height < 2 {
126            return;
127        }
128
129        let title = "tui-scrollbar - mouse scroll demo";
130        let block = Block::new()
131            .borders(Borders::TOP)
132            .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133            .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134            .title(
135                Line::from(title)
136                    .centered()
137                    .fg(TITLE_FG)
138                    .bg(TITLE_BG)
139                    .bold(),
140            );
141        frame.render_widget(&block, area);
142
143        let content_area = Rect {
144            y: area.y.saturating_add(1),
145            height: area.height.saturating_sub(1),
146            ..area
147        };
148        let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149        let help_area = Rect {
150            x: content_area.x.saturating_add(1),
151            y: content_area.y,
152            width: content_area.width.saturating_sub(1),
153            height: 1,
154        };
155        if help_area.width > 0 {
156            frame.render_widget(
157                Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158                help_area,
159            );
160        }
161        let content_area = Rect {
162            y: content_area.y.saturating_add(1),
163            height: content_area.height.saturating_sub(1),
164            ..content_area
165        };
166
167        // Split out the bottom row and right column for the scrollbars.
168        let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169            Constraint::Fill(1),
170            Constraint::Length(1),
171        ]));
172        let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173            Constraint::Fill(1),
174            Constraint::Length(1),
175        ]));
176        let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177            Constraint::Fill(1),
178            Constraint::Length(1),
179        ]));
180
181        self.layout = Some(LayoutState {
182            content,
183            vertical_bar,
184            horizontal_bar,
185        });
186
187        // Keep offsets valid when the terminal is resized.
188        let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189        self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190        self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192        let horizontal_lengths = ScrollLengths {
193            content_len: h_metrics.content_len(),
194            viewport_len: h_metrics.viewport_len(),
195        };
196        let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197        let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198        let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199        let horizontal = ScrollBar::horizontal(horizontal_lengths)
200            .arrows(ScrollBarArrows::Both)
201            .offset(self.horizontal_offset)
202            .scroll_step(SUBCELL)
203            .track_style(track_style)
204            .thumb_style(thumb_style)
205            .arrow_style(arrow_style);
206        let vertical_lengths = ScrollLengths {
207            content_len: v_metrics.content_len(),
208            viewport_len: v_metrics.viewport_len(),
209        };
210        let vertical = ScrollBar::vertical(vertical_lengths)
211            .arrows(ScrollBarArrows::Both)
212            .offset(self.vertical_offset)
213            .scroll_step(SUBCELL)
214            .track_style(track_style)
215            .thumb_style(thumb_style)
216            .arrow_style(arrow_style);
217
218        frame.render_widget(&horizontal, horizontal_bar);
219        frame.render_widget(&vertical, vertical_bar);
220    }
221
222    /// Handles keyboard and mouse events, updating offsets as needed.
223    fn handle_events(&mut self) -> Result<()> {
224        match event::read()? {
225            Event::Key(key) => {
226                if key.is_press() {
227                    self.handle_key_event(key.code);
228                }
229            }
230            Event::Mouse(event) => {
231                self.handle_mouse_event(event);
232            }
233            _ => {}
234        }
235        Ok(())
236    }
237
238    /// Handles keyboard input, updating offsets or exiting as needed.
239    fn handle_key_event(&mut self, code: KeyCode) {
240        match code {
241            KeyCode::Char('q') | KeyCode::Esc => self.state = AppState::Quit,
242            KeyCode::Up | KeyCode::Char('k') => self.handle_key_scroll(0, -(KEY_STEP as isize)),
243            KeyCode::Down | KeyCode::Char('j') => self.handle_key_scroll(0, KEY_STEP as isize),
244            KeyCode::Left | KeyCode::Char('h') => self.handle_key_scroll(-(KEY_STEP as isize), 0),
245            KeyCode::Right | KeyCode::Char('l') => self.handle_key_scroll(KEY_STEP as isize, 0),
246            _ => {}
247        }
248    }
249
250    /// Applies a keyboard delta to the scrollbar offsets.
251    fn handle_key_scroll(&mut self, dx: isize, dy: isize) {
252        let Some(layout) = self.layout else {
253            return;
254        };
255        let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
256        self.horizontal_offset =
257            Self::apply_delta(self.horizontal_offset, dx, h_metrics.max_offset());
258        self.vertical_offset = Self::apply_delta(self.vertical_offset, dy, v_metrics.max_offset());
259    }
260
261    /// Handles crossterm mouse events using the scrollbar helpers.
262    fn handle_mouse_event(&mut self, event: event::MouseEvent) {
263        let Some(layout) = self.layout else {
264            return;
265        };
266        let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
267        let horizontal = self.horizontal_scrollbar(h_metrics);
268        let vertical = self.vertical_scrollbar(v_metrics);
269
270        if let Some(command) = horizontal.handle_mouse_event(
271            layout.horizontal_bar,
272            event,
273            &mut self.horizontal_interaction,
274        ) {
275            self.apply_command(command, true);
276        }
277        if let Some(command) =
278            vertical.handle_mouse_event(layout.vertical_bar, event, &mut self.vertical_interaction)
279        {
280            self.apply_command(command, false);
281        }
282    }
283
284    /// Applies a scroll command to the current axis offset.
285    fn apply_command(&mut self, command: ScrollCommand, is_horizontal: bool) {
286        let ScrollCommand::SetOffset(offset) = command;
287        if is_horizontal {
288            self.horizontal_offset = offset;
289        } else {
290            self.vertical_offset = offset;
291        }
292    }
293
294    /// Builds a horizontal scrollbar from the current metrics.
295    fn horizontal_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
296        let lengths = ScrollLengths {
297            content_len: metrics.content_len(),
298            viewport_len: metrics.viewport_len(),
299        };
300        ScrollBar::horizontal(lengths)
301            .arrows(ScrollBarArrows::Both)
302            .offset(self.horizontal_offset)
303            .scroll_step(SUBCELL)
304    }
Source

pub const fn orientation(self, orientation: ScrollBarOrientation) -> Self

Sets the scrollbar orientation.

This is mostly useful when sharing a builder chain and choosing the orientation later.

use tui_scrollbar::{ScrollBar, ScrollBarOrientation, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).orientation(ScrollBarOrientation::Horizontal);
Source

pub const fn content_len(self, content_len: usize) -> Self

Sets the total scrollable content length in logical units.

Larger values shrink the thumb, while smaller values enlarge it.

Zero values are treated as 1.

use tui_scrollbar::{ScrollBar, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).content_len(240);
Source

pub const fn viewport_len(self, viewport_len: usize) -> Self

Sets the visible viewport length in logical units.

When viewport_len >= content_len, the thumb fills the track.

Zero values are treated as 1.

use tui_scrollbar::{ScrollBar, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).viewport_len(60);
Source

pub const fn offset(self, offset: usize) -> Self

Sets the current scroll offset in logical units.

Offsets are clamped to content_len - viewport_len during rendering and input handling, not when this builder is called.

use tui_scrollbar::{ScrollBar, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).offset(30);
Examples found in repository?
examples/scrollbar.rs (line 146)
127fn render_horizontal_steps(frame: &mut ratatui::Frame, cells: Vec<Rect>) {
128    for (index, area) in cells.iter().enumerate() {
129        let [label_area, bar_area] = area.layout(&Layout::horizontal([
130            Constraint::Length(2),
131            Constraint::Fill(1),
132        ]));
133        if bar_area.width == 0 {
134            continue;
135        }
136        let metrics = build_metrics(bar_area.width as usize, 6);
137        let (label, thumb_start) = step_entry(&metrics, index);
138        let label = (label % 8).to_string();
139        let offset = metrics.offset_for_thumb_start(thumb_start);
140        let lengths = ScrollLengths {
141            content_len: metrics.content_len(),
142            viewport_len: metrics.viewport_len(),
143        };
144        let scrollbar = ScrollBar::horizontal(lengths)
145            .arrows(ScrollBarArrows::Both)
146            .offset(offset);
147        render_label(frame, label_area, &label);
148        frame.render_widget(&scrollbar, bar_area);
149    }
150}
151
152/// Draws vertical scrollbars that sweep every 1/8th thumb position, left to right.
153fn render_vertical_steps(frame: &mut ratatui::Frame, cells: Vec<Rect>) {
154    for (index, area) in cells.iter().enumerate() {
155        let [label_area, bar_area] = area.layout(&Layout::vertical([
156            Constraint::Length(1),
157            Constraint::Fill(1),
158        ]));
159        if bar_area.height == 0 {
160            continue;
161        }
162        let metrics = build_metrics(bar_area.height as usize, 3);
163        let (label, thumb_start) = step_entry(&metrics, index);
164        let label = (label % 8).to_string();
165        let offset = metrics.offset_for_thumb_start(thumb_start);
166        let lengths = ScrollLengths {
167            content_len: metrics.content_len(),
168            viewport_len: metrics.viewport_len(),
169        };
170        let scrollbar = ScrollBar::vertical(lengths)
171            .arrows(ScrollBarArrows::Both)
172            .offset(offset);
173        render_label(frame, label_area, &label);
174        frame.render_widget(&scrollbar, bar_area);
175    }
176}
More examples
Hide additional examples
examples/scrollbar_styled.rs (line 59)
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25    if area.width < 2 || area.height < 2 {
26        return;
27    }
28
29    let horizontal_bar = area
30        .rows()
31        .next_back()
32        .unwrap_or(area)
33        .inner(Margin::new(1, 0));
34    let vertical_bar = area
35        .columns()
36        .next_back()
37        .unwrap_or(area)
38        .inner(Margin::new(0, 1));
39
40    let block = Block::new()
41        .borders(Borders::ALL)
42        .title("styled scrollbars")
43        .border_style((Color::LightBlue, Color::Black))
44        .style((Color::Gray, Color::Black));
45    let content = block.inner(area).inner(Margin::new(2, 1));
46    frame.render_widget(block, area);
47
48    frame.render_widget(
49        Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50            .style((Color::Gray, Color::Black)),
51        content,
52    );
53
54    let lengths = ScrollLengths {
55        content_len: 160,
56        viewport_len: 40,
57    };
58    let horizontal = ScrollBar::horizontal(lengths)
59        .offset(48)
60        .arrows(ScrollBarArrows::Both)
61        .glyph_set(GlyphSet::box_drawing())
62        .track_style((Color::Blue, Color::Black).into())
63        .thumb_style((Color::Yellow, Modifier::BOLD).into())
64        .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65    let vertical = ScrollBar::vertical(lengths)
66        .offset(80)
67        .arrows(ScrollBarArrows::Both)
68        .glyph_set(GlyphSet::box_drawing())
69        .track_style((Color::Magenta, Color::Black).into())
70        .thumb_style((Color::Cyan, Modifier::BOLD).into())
71        .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73    frame.render_widget(&horizontal, horizontal_bar);
74    frame.render_widget(&vertical, vertical_bar);
75}
examples/scrollbar_mouse.rs (line 201)
123    fn render(&mut self, frame: &mut ratatui::Frame) {
124        let area = frame.area();
125        if area.width < 2 || area.height < 2 {
126            return;
127        }
128
129        let title = "tui-scrollbar - mouse scroll demo";
130        let block = Block::new()
131            .borders(Borders::TOP)
132            .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133            .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134            .title(
135                Line::from(title)
136                    .centered()
137                    .fg(TITLE_FG)
138                    .bg(TITLE_BG)
139                    .bold(),
140            );
141        frame.render_widget(&block, area);
142
143        let content_area = Rect {
144            y: area.y.saturating_add(1),
145            height: area.height.saturating_sub(1),
146            ..area
147        };
148        let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149        let help_area = Rect {
150            x: content_area.x.saturating_add(1),
151            y: content_area.y,
152            width: content_area.width.saturating_sub(1),
153            height: 1,
154        };
155        if help_area.width > 0 {
156            frame.render_widget(
157                Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158                help_area,
159            );
160        }
161        let content_area = Rect {
162            y: content_area.y.saturating_add(1),
163            height: content_area.height.saturating_sub(1),
164            ..content_area
165        };
166
167        // Split out the bottom row and right column for the scrollbars.
168        let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169            Constraint::Fill(1),
170            Constraint::Length(1),
171        ]));
172        let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173            Constraint::Fill(1),
174            Constraint::Length(1),
175        ]));
176        let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177            Constraint::Fill(1),
178            Constraint::Length(1),
179        ]));
180
181        self.layout = Some(LayoutState {
182            content,
183            vertical_bar,
184            horizontal_bar,
185        });
186
187        // Keep offsets valid when the terminal is resized.
188        let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189        self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190        self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192        let horizontal_lengths = ScrollLengths {
193            content_len: h_metrics.content_len(),
194            viewport_len: h_metrics.viewport_len(),
195        };
196        let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197        let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198        let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199        let horizontal = ScrollBar::horizontal(horizontal_lengths)
200            .arrows(ScrollBarArrows::Both)
201            .offset(self.horizontal_offset)
202            .scroll_step(SUBCELL)
203            .track_style(track_style)
204            .thumb_style(thumb_style)
205            .arrow_style(arrow_style);
206        let vertical_lengths = ScrollLengths {
207            content_len: v_metrics.content_len(),
208            viewport_len: v_metrics.viewport_len(),
209        };
210        let vertical = ScrollBar::vertical(vertical_lengths)
211            .arrows(ScrollBarArrows::Both)
212            .offset(self.vertical_offset)
213            .scroll_step(SUBCELL)
214            .track_style(track_style)
215            .thumb_style(thumb_style)
216            .arrow_style(arrow_style);
217
218        frame.render_widget(&horizontal, horizontal_bar);
219        frame.render_widget(&vertical, vertical_bar);
220    }
221
222    /// Handles keyboard and mouse events, updating offsets as needed.
223    fn handle_events(&mut self) -> Result<()> {
224        match event::read()? {
225            Event::Key(key) => {
226                if key.is_press() {
227                    self.handle_key_event(key.code);
228                }
229            }
230            Event::Mouse(event) => {
231                self.handle_mouse_event(event);
232            }
233            _ => {}
234        }
235        Ok(())
236    }
237
238    /// Handles keyboard input, updating offsets or exiting as needed.
239    fn handle_key_event(&mut self, code: KeyCode) {
240        match code {
241            KeyCode::Char('q') | KeyCode::Esc => self.state = AppState::Quit,
242            KeyCode::Up | KeyCode::Char('k') => self.handle_key_scroll(0, -(KEY_STEP as isize)),
243            KeyCode::Down | KeyCode::Char('j') => self.handle_key_scroll(0, KEY_STEP as isize),
244            KeyCode::Left | KeyCode::Char('h') => self.handle_key_scroll(-(KEY_STEP as isize), 0),
245            KeyCode::Right | KeyCode::Char('l') => self.handle_key_scroll(KEY_STEP as isize, 0),
246            _ => {}
247        }
248    }
249
250    /// Applies a keyboard delta to the scrollbar offsets.
251    fn handle_key_scroll(&mut self, dx: isize, dy: isize) {
252        let Some(layout) = self.layout else {
253            return;
254        };
255        let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
256        self.horizontal_offset =
257            Self::apply_delta(self.horizontal_offset, dx, h_metrics.max_offset());
258        self.vertical_offset = Self::apply_delta(self.vertical_offset, dy, v_metrics.max_offset());
259    }
260
261    /// Handles crossterm mouse events using the scrollbar helpers.
262    fn handle_mouse_event(&mut self, event: event::MouseEvent) {
263        let Some(layout) = self.layout else {
264            return;
265        };
266        let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
267        let horizontal = self.horizontal_scrollbar(h_metrics);
268        let vertical = self.vertical_scrollbar(v_metrics);
269
270        if let Some(command) = horizontal.handle_mouse_event(
271            layout.horizontal_bar,
272            event,
273            &mut self.horizontal_interaction,
274        ) {
275            self.apply_command(command, true);
276        }
277        if let Some(command) =
278            vertical.handle_mouse_event(layout.vertical_bar, event, &mut self.vertical_interaction)
279        {
280            self.apply_command(command, false);
281        }
282    }
283
284    /// Applies a scroll command to the current axis offset.
285    fn apply_command(&mut self, command: ScrollCommand, is_horizontal: bool) {
286        let ScrollCommand::SetOffset(offset) = command;
287        if is_horizontal {
288            self.horizontal_offset = offset;
289        } else {
290            self.vertical_offset = offset;
291        }
292    }
293
294    /// Builds a horizontal scrollbar from the current metrics.
295    fn horizontal_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
296        let lengths = ScrollLengths {
297            content_len: metrics.content_len(),
298            viewport_len: metrics.viewport_len(),
299        };
300        ScrollBar::horizontal(lengths)
301            .arrows(ScrollBarArrows::Both)
302            .offset(self.horizontal_offset)
303            .scroll_step(SUBCELL)
304    }
305
306    /// Builds a vertical scrollbar from the current metrics.
307    fn vertical_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
308        let lengths = ScrollLengths {
309            content_len: metrics.content_len(),
310            viewport_len: metrics.viewport_len(),
311        };
312        ScrollBar::vertical(lengths)
313            .arrows(ScrollBarArrows::Both)
314            .offset(self.vertical_offset)
315            .scroll_step(SUBCELL)
316    }
Source

pub const fn track_style(self, style: Style) -> Self

Sets the style applied to track glyphs.

Track styling applies only to cells where the thumb is not rendered.

use ratatui_core::style::{Color, Style};
use tui_scrollbar::{ScrollBar, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).track_style(Style::new().bg(Color::Black));
Examples found in repository?
examples/scrollbar_styled.rs (line 62)
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25    if area.width < 2 || area.height < 2 {
26        return;
27    }
28
29    let horizontal_bar = area
30        .rows()
31        .next_back()
32        .unwrap_or(area)
33        .inner(Margin::new(1, 0));
34    let vertical_bar = area
35        .columns()
36        .next_back()
37        .unwrap_or(area)
38        .inner(Margin::new(0, 1));
39
40    let block = Block::new()
41        .borders(Borders::ALL)
42        .title("styled scrollbars")
43        .border_style((Color::LightBlue, Color::Black))
44        .style((Color::Gray, Color::Black));
45    let content = block.inner(area).inner(Margin::new(2, 1));
46    frame.render_widget(block, area);
47
48    frame.render_widget(
49        Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50            .style((Color::Gray, Color::Black)),
51        content,
52    );
53
54    let lengths = ScrollLengths {
55        content_len: 160,
56        viewport_len: 40,
57    };
58    let horizontal = ScrollBar::horizontal(lengths)
59        .offset(48)
60        .arrows(ScrollBarArrows::Both)
61        .glyph_set(GlyphSet::box_drawing())
62        .track_style((Color::Blue, Color::Black).into())
63        .thumb_style((Color::Yellow, Modifier::BOLD).into())
64        .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65    let vertical = ScrollBar::vertical(lengths)
66        .offset(80)
67        .arrows(ScrollBarArrows::Both)
68        .glyph_set(GlyphSet::box_drawing())
69        .track_style((Color::Magenta, Color::Black).into())
70        .thumb_style((Color::Cyan, Modifier::BOLD).into())
71        .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73    frame.render_widget(&horizontal, horizontal_bar);
74    frame.render_widget(&vertical, vertical_bar);
75}
More examples
Hide additional examples
examples/scrollbar_mouse.rs (line 203)
123    fn render(&mut self, frame: &mut ratatui::Frame) {
124        let area = frame.area();
125        if area.width < 2 || area.height < 2 {
126            return;
127        }
128
129        let title = "tui-scrollbar - mouse scroll demo";
130        let block = Block::new()
131            .borders(Borders::TOP)
132            .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133            .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134            .title(
135                Line::from(title)
136                    .centered()
137                    .fg(TITLE_FG)
138                    .bg(TITLE_BG)
139                    .bold(),
140            );
141        frame.render_widget(&block, area);
142
143        let content_area = Rect {
144            y: area.y.saturating_add(1),
145            height: area.height.saturating_sub(1),
146            ..area
147        };
148        let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149        let help_area = Rect {
150            x: content_area.x.saturating_add(1),
151            y: content_area.y,
152            width: content_area.width.saturating_sub(1),
153            height: 1,
154        };
155        if help_area.width > 0 {
156            frame.render_widget(
157                Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158                help_area,
159            );
160        }
161        let content_area = Rect {
162            y: content_area.y.saturating_add(1),
163            height: content_area.height.saturating_sub(1),
164            ..content_area
165        };
166
167        // Split out the bottom row and right column for the scrollbars.
168        let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169            Constraint::Fill(1),
170            Constraint::Length(1),
171        ]));
172        let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173            Constraint::Fill(1),
174            Constraint::Length(1),
175        ]));
176        let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177            Constraint::Fill(1),
178            Constraint::Length(1),
179        ]));
180
181        self.layout = Some(LayoutState {
182            content,
183            vertical_bar,
184            horizontal_bar,
185        });
186
187        // Keep offsets valid when the terminal is resized.
188        let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189        self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190        self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192        let horizontal_lengths = ScrollLengths {
193            content_len: h_metrics.content_len(),
194            viewport_len: h_metrics.viewport_len(),
195        };
196        let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197        let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198        let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199        let horizontal = ScrollBar::horizontal(horizontal_lengths)
200            .arrows(ScrollBarArrows::Both)
201            .offset(self.horizontal_offset)
202            .scroll_step(SUBCELL)
203            .track_style(track_style)
204            .thumb_style(thumb_style)
205            .arrow_style(arrow_style);
206        let vertical_lengths = ScrollLengths {
207            content_len: v_metrics.content_len(),
208            viewport_len: v_metrics.viewport_len(),
209        };
210        let vertical = ScrollBar::vertical(vertical_lengths)
211            .arrows(ScrollBarArrows::Both)
212            .offset(self.vertical_offset)
213            .scroll_step(SUBCELL)
214            .track_style(track_style)
215            .thumb_style(thumb_style)
216            .arrow_style(arrow_style);
217
218        frame.render_widget(&horizontal, horizontal_bar);
219        frame.render_widget(&vertical, vertical_bar);
220    }
Source

pub const fn thumb_style(self, style: Style) -> Self

Sets the style applied to thumb glyphs.

Thumb styling applies to full and partial thumb cells. Thumb glyphs are block characters, so Style::fg usually controls the visible thumb color. Use Style::bg only when the cell behind the glyph should differ from the track. On partial thumb cells, the background can show at the thumb ends.

use ratatui_core::style::{Color, Style};
use tui_scrollbar::{ScrollBar, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 40,
};
let scrollbar =
    ScrollBar::vertical(lengths).thumb_style(Style::new().fg(Color::Rgb(255, 158, 100)));
Examples found in repository?
examples/scrollbar_styled.rs (line 63)
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25    if area.width < 2 || area.height < 2 {
26        return;
27    }
28
29    let horizontal_bar = area
30        .rows()
31        .next_back()
32        .unwrap_or(area)
33        .inner(Margin::new(1, 0));
34    let vertical_bar = area
35        .columns()
36        .next_back()
37        .unwrap_or(area)
38        .inner(Margin::new(0, 1));
39
40    let block = Block::new()
41        .borders(Borders::ALL)
42        .title("styled scrollbars")
43        .border_style((Color::LightBlue, Color::Black))
44        .style((Color::Gray, Color::Black));
45    let content = block.inner(area).inner(Margin::new(2, 1));
46    frame.render_widget(block, area);
47
48    frame.render_widget(
49        Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50            .style((Color::Gray, Color::Black)),
51        content,
52    );
53
54    let lengths = ScrollLengths {
55        content_len: 160,
56        viewport_len: 40,
57    };
58    let horizontal = ScrollBar::horizontal(lengths)
59        .offset(48)
60        .arrows(ScrollBarArrows::Both)
61        .glyph_set(GlyphSet::box_drawing())
62        .track_style((Color::Blue, Color::Black).into())
63        .thumb_style((Color::Yellow, Modifier::BOLD).into())
64        .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65    let vertical = ScrollBar::vertical(lengths)
66        .offset(80)
67        .arrows(ScrollBarArrows::Both)
68        .glyph_set(GlyphSet::box_drawing())
69        .track_style((Color::Magenta, Color::Black).into())
70        .thumb_style((Color::Cyan, Modifier::BOLD).into())
71        .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73    frame.render_widget(&horizontal, horizontal_bar);
74    frame.render_widget(&vertical, vertical_bar);
75}
More examples
Hide additional examples
examples/scrollbar_mouse.rs (line 204)
123    fn render(&mut self, frame: &mut ratatui::Frame) {
124        let area = frame.area();
125        if area.width < 2 || area.height < 2 {
126            return;
127        }
128
129        let title = "tui-scrollbar - mouse scroll demo";
130        let block = Block::new()
131            .borders(Borders::TOP)
132            .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133            .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134            .title(
135                Line::from(title)
136                    .centered()
137                    .fg(TITLE_FG)
138                    .bg(TITLE_BG)
139                    .bold(),
140            );
141        frame.render_widget(&block, area);
142
143        let content_area = Rect {
144            y: area.y.saturating_add(1),
145            height: area.height.saturating_sub(1),
146            ..area
147        };
148        let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149        let help_area = Rect {
150            x: content_area.x.saturating_add(1),
151            y: content_area.y,
152            width: content_area.width.saturating_sub(1),
153            height: 1,
154        };
155        if help_area.width > 0 {
156            frame.render_widget(
157                Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158                help_area,
159            );
160        }
161        let content_area = Rect {
162            y: content_area.y.saturating_add(1),
163            height: content_area.height.saturating_sub(1),
164            ..content_area
165        };
166
167        // Split out the bottom row and right column for the scrollbars.
168        let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169            Constraint::Fill(1),
170            Constraint::Length(1),
171        ]));
172        let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173            Constraint::Fill(1),
174            Constraint::Length(1),
175        ]));
176        let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177            Constraint::Fill(1),
178            Constraint::Length(1),
179        ]));
180
181        self.layout = Some(LayoutState {
182            content,
183            vertical_bar,
184            horizontal_bar,
185        });
186
187        // Keep offsets valid when the terminal is resized.
188        let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189        self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190        self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192        let horizontal_lengths = ScrollLengths {
193            content_len: h_metrics.content_len(),
194            viewport_len: h_metrics.viewport_len(),
195        };
196        let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197        let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198        let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199        let horizontal = ScrollBar::horizontal(horizontal_lengths)
200            .arrows(ScrollBarArrows::Both)
201            .offset(self.horizontal_offset)
202            .scroll_step(SUBCELL)
203            .track_style(track_style)
204            .thumb_style(thumb_style)
205            .arrow_style(arrow_style);
206        let vertical_lengths = ScrollLengths {
207            content_len: v_metrics.content_len(),
208            viewport_len: v_metrics.viewport_len(),
209        };
210        let vertical = ScrollBar::vertical(vertical_lengths)
211            .arrows(ScrollBarArrows::Both)
212            .offset(self.vertical_offset)
213            .scroll_step(SUBCELL)
214            .track_style(track_style)
215            .thumb_style(thumb_style)
216            .arrow_style(arrow_style);
217
218        frame.render_widget(&horizontal, horizontal_bar);
219        frame.render_widget(&vertical, vertical_bar);
220    }
Source

pub const fn arrow_style(self, style: Style) -> Self

Sets the style applied to arrow glyphs.

Arrow endcaps render only when enabled with Self::arrows. If no arrow style is configured internally, arrows fall back to the track style.

use ratatui_core::style::{Color, Style};
use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths)
    .arrows(ScrollBarArrows::Both)
    .arrow_style(Style::new().fg(Color::Yellow).bg(Color::Black));
Examples found in repository?
examples/scrollbar_styled.rs (line 64)
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25    if area.width < 2 || area.height < 2 {
26        return;
27    }
28
29    let horizontal_bar = area
30        .rows()
31        .next_back()
32        .unwrap_or(area)
33        .inner(Margin::new(1, 0));
34    let vertical_bar = area
35        .columns()
36        .next_back()
37        .unwrap_or(area)
38        .inner(Margin::new(0, 1));
39
40    let block = Block::new()
41        .borders(Borders::ALL)
42        .title("styled scrollbars")
43        .border_style((Color::LightBlue, Color::Black))
44        .style((Color::Gray, Color::Black));
45    let content = block.inner(area).inner(Margin::new(2, 1));
46    frame.render_widget(block, area);
47
48    frame.render_widget(
49        Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50            .style((Color::Gray, Color::Black)),
51        content,
52    );
53
54    let lengths = ScrollLengths {
55        content_len: 160,
56        viewport_len: 40,
57    };
58    let horizontal = ScrollBar::horizontal(lengths)
59        .offset(48)
60        .arrows(ScrollBarArrows::Both)
61        .glyph_set(GlyphSet::box_drawing())
62        .track_style((Color::Blue, Color::Black).into())
63        .thumb_style((Color::Yellow, Modifier::BOLD).into())
64        .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65    let vertical = ScrollBar::vertical(lengths)
66        .offset(80)
67        .arrows(ScrollBarArrows::Both)
68        .glyph_set(GlyphSet::box_drawing())
69        .track_style((Color::Magenta, Color::Black).into())
70        .thumb_style((Color::Cyan, Modifier::BOLD).into())
71        .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73    frame.render_widget(&horizontal, horizontal_bar);
74    frame.render_widget(&vertical, vertical_bar);
75}
More examples
Hide additional examples
examples/scrollbar_mouse.rs (line 205)
123    fn render(&mut self, frame: &mut ratatui::Frame) {
124        let area = frame.area();
125        if area.width < 2 || area.height < 2 {
126            return;
127        }
128
129        let title = "tui-scrollbar - mouse scroll demo";
130        let block = Block::new()
131            .borders(Borders::TOP)
132            .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133            .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134            .title(
135                Line::from(title)
136                    .centered()
137                    .fg(TITLE_FG)
138                    .bg(TITLE_BG)
139                    .bold(),
140            );
141        frame.render_widget(&block, area);
142
143        let content_area = Rect {
144            y: area.y.saturating_add(1),
145            height: area.height.saturating_sub(1),
146            ..area
147        };
148        let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149        let help_area = Rect {
150            x: content_area.x.saturating_add(1),
151            y: content_area.y,
152            width: content_area.width.saturating_sub(1),
153            height: 1,
154        };
155        if help_area.width > 0 {
156            frame.render_widget(
157                Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158                help_area,
159            );
160        }
161        let content_area = Rect {
162            y: content_area.y.saturating_add(1),
163            height: content_area.height.saturating_sub(1),
164            ..content_area
165        };
166
167        // Split out the bottom row and right column for the scrollbars.
168        let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169            Constraint::Fill(1),
170            Constraint::Length(1),
171        ]));
172        let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173            Constraint::Fill(1),
174            Constraint::Length(1),
175        ]));
176        let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177            Constraint::Fill(1),
178            Constraint::Length(1),
179        ]));
180
181        self.layout = Some(LayoutState {
182            content,
183            vertical_bar,
184            horizontal_bar,
185        });
186
187        // Keep offsets valid when the terminal is resized.
188        let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189        self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190        self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192        let horizontal_lengths = ScrollLengths {
193            content_len: h_metrics.content_len(),
194            viewport_len: h_metrics.viewport_len(),
195        };
196        let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197        let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198        let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199        let horizontal = ScrollBar::horizontal(horizontal_lengths)
200            .arrows(ScrollBarArrows::Both)
201            .offset(self.horizontal_offset)
202            .scroll_step(SUBCELL)
203            .track_style(track_style)
204            .thumb_style(thumb_style)
205            .arrow_style(arrow_style);
206        let vertical_lengths = ScrollLengths {
207            content_len: v_metrics.content_len(),
208            viewport_len: v_metrics.viewport_len(),
209        };
210        let vertical = ScrollBar::vertical(vertical_lengths)
211            .arrows(ScrollBarArrows::Both)
212            .offset(self.vertical_offset)
213            .scroll_step(SUBCELL)
214            .track_style(track_style)
215            .thumb_style(thumb_style)
216            .arrow_style(arrow_style);
217
218        frame.render_widget(&horizontal, horizontal_bar);
219        frame.render_widget(&vertical, vertical_bar);
220    }
Source

pub const fn glyph_set(self, glyph_set: GlyphSet) -> Self

Selects the glyph set used to render the track and thumb.

GlyphSet::symbols_for_legacy_computing uses Symbols for Legacy Computing for 1/8th upper/right fills. Use GlyphSet::unicode if you want to avoid the legacy supplement, or GlyphSet::box_drawing when you want a visible line track.

use tui_scrollbar::{GlyphSet, ScrollBar, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).glyph_set(GlyphSet::unicode());
Examples found in repository?
examples/scrollbar_styled.rs (line 61)
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25    if area.width < 2 || area.height < 2 {
26        return;
27    }
28
29    let horizontal_bar = area
30        .rows()
31        .next_back()
32        .unwrap_or(area)
33        .inner(Margin::new(1, 0));
34    let vertical_bar = area
35        .columns()
36        .next_back()
37        .unwrap_or(area)
38        .inner(Margin::new(0, 1));
39
40    let block = Block::new()
41        .borders(Borders::ALL)
42        .title("styled scrollbars")
43        .border_style((Color::LightBlue, Color::Black))
44        .style((Color::Gray, Color::Black));
45    let content = block.inner(area).inner(Margin::new(2, 1));
46    frame.render_widget(block, area);
47
48    frame.render_widget(
49        Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50            .style((Color::Gray, Color::Black)),
51        content,
52    );
53
54    let lengths = ScrollLengths {
55        content_len: 160,
56        viewport_len: 40,
57    };
58    let horizontal = ScrollBar::horizontal(lengths)
59        .offset(48)
60        .arrows(ScrollBarArrows::Both)
61        .glyph_set(GlyphSet::box_drawing())
62        .track_style((Color::Blue, Color::Black).into())
63        .thumb_style((Color::Yellow, Modifier::BOLD).into())
64        .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65    let vertical = ScrollBar::vertical(lengths)
66        .offset(80)
67        .arrows(ScrollBarArrows::Both)
68        .glyph_set(GlyphSet::box_drawing())
69        .track_style((Color::Magenta, Color::Black).into())
70        .thumb_style((Color::Cyan, Modifier::BOLD).into())
71        .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73    frame.render_widget(&horizontal, horizontal_bar);
74    frame.render_widget(&vertical, vertical_bar);
75}
Source

pub const fn arrows(self, arrows: ScrollBarArrows) -> Self

Sets which arrow endcaps are rendered.

Each enabled arrow reserves one cell at the start or end of the track.

use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).arrows(ScrollBarArrows::Both);
Examples found in repository?
examples/scrollbar.rs (line 145)
127fn render_horizontal_steps(frame: &mut ratatui::Frame, cells: Vec<Rect>) {
128    for (index, area) in cells.iter().enumerate() {
129        let [label_area, bar_area] = area.layout(&Layout::horizontal([
130            Constraint::Length(2),
131            Constraint::Fill(1),
132        ]));
133        if bar_area.width == 0 {
134            continue;
135        }
136        let metrics = build_metrics(bar_area.width as usize, 6);
137        let (label, thumb_start) = step_entry(&metrics, index);
138        let label = (label % 8).to_string();
139        let offset = metrics.offset_for_thumb_start(thumb_start);
140        let lengths = ScrollLengths {
141            content_len: metrics.content_len(),
142            viewport_len: metrics.viewport_len(),
143        };
144        let scrollbar = ScrollBar::horizontal(lengths)
145            .arrows(ScrollBarArrows::Both)
146            .offset(offset);
147        render_label(frame, label_area, &label);
148        frame.render_widget(&scrollbar, bar_area);
149    }
150}
151
152/// Draws vertical scrollbars that sweep every 1/8th thumb position, left to right.
153fn render_vertical_steps(frame: &mut ratatui::Frame, cells: Vec<Rect>) {
154    for (index, area) in cells.iter().enumerate() {
155        let [label_area, bar_area] = area.layout(&Layout::vertical([
156            Constraint::Length(1),
157            Constraint::Fill(1),
158        ]));
159        if bar_area.height == 0 {
160            continue;
161        }
162        let metrics = build_metrics(bar_area.height as usize, 3);
163        let (label, thumb_start) = step_entry(&metrics, index);
164        let label = (label % 8).to_string();
165        let offset = metrics.offset_for_thumb_start(thumb_start);
166        let lengths = ScrollLengths {
167            content_len: metrics.content_len(),
168            viewport_len: metrics.viewport_len(),
169        };
170        let scrollbar = ScrollBar::vertical(lengths)
171            .arrows(ScrollBarArrows::Both)
172            .offset(offset);
173        render_label(frame, label_area, &label);
174        frame.render_widget(&scrollbar, bar_area);
175    }
176}
More examples
Hide additional examples
examples/scrollbar_styled.rs (line 60)
24fn render(area: Rect, frame: &mut ratatui::Frame) {
25    if area.width < 2 || area.height < 2 {
26        return;
27    }
28
29    let horizontal_bar = area
30        .rows()
31        .next_back()
32        .unwrap_or(area)
33        .inner(Margin::new(1, 0));
34    let vertical_bar = area
35        .columns()
36        .next_back()
37        .unwrap_or(area)
38        .inner(Margin::new(0, 1));
39
40    let block = Block::new()
41        .borders(Borders::ALL)
42        .title("styled scrollbars")
43        .border_style((Color::LightBlue, Color::Black))
44        .style((Color::Gray, Color::Black));
45    let content = block.inner(area).inner(Margin::new(2, 1));
46    frame.render_widget(block, area);
47
48    frame.render_widget(
49        Paragraph::new("track_style, thumb_style, and arrow_style can each use distinct colors")
50            .style((Color::Gray, Color::Black)),
51        content,
52    );
53
54    let lengths = ScrollLengths {
55        content_len: 160,
56        viewport_len: 40,
57    };
58    let horizontal = ScrollBar::horizontal(lengths)
59        .offset(48)
60        .arrows(ScrollBarArrows::Both)
61        .glyph_set(GlyphSet::box_drawing())
62        .track_style((Color::Blue, Color::Black).into())
63        .thumb_style((Color::Yellow, Modifier::BOLD).into())
64        .arrow_style((Color::LightGreen, Color::Black, Modifier::BOLD).into());
65    let vertical = ScrollBar::vertical(lengths)
66        .offset(80)
67        .arrows(ScrollBarArrows::Both)
68        .glyph_set(GlyphSet::box_drawing())
69        .track_style((Color::Magenta, Color::Black).into())
70        .thumb_style((Color::Cyan, Modifier::BOLD).into())
71        .arrow_style((Color::LightRed, Color::Black, Modifier::BOLD).into());
72
73    frame.render_widget(&horizontal, horizontal_bar);
74    frame.render_widget(&vertical, vertical_bar);
75}
examples/scrollbar_mouse.rs (line 200)
123    fn render(&mut self, frame: &mut ratatui::Frame) {
124        let area = frame.area();
125        if area.width < 2 || area.height < 2 {
126            return;
127        }
128
129        let title = "tui-scrollbar - mouse scroll demo";
130        let block = Block::new()
131            .borders(Borders::TOP)
132            .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133            .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134            .title(
135                Line::from(title)
136                    .centered()
137                    .fg(TITLE_FG)
138                    .bg(TITLE_BG)
139                    .bold(),
140            );
141        frame.render_widget(&block, area);
142
143        let content_area = Rect {
144            y: area.y.saturating_add(1),
145            height: area.height.saturating_sub(1),
146            ..area
147        };
148        let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149        let help_area = Rect {
150            x: content_area.x.saturating_add(1),
151            y: content_area.y,
152            width: content_area.width.saturating_sub(1),
153            height: 1,
154        };
155        if help_area.width > 0 {
156            frame.render_widget(
157                Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158                help_area,
159            );
160        }
161        let content_area = Rect {
162            y: content_area.y.saturating_add(1),
163            height: content_area.height.saturating_sub(1),
164            ..content_area
165        };
166
167        // Split out the bottom row and right column for the scrollbars.
168        let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169            Constraint::Fill(1),
170            Constraint::Length(1),
171        ]));
172        let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173            Constraint::Fill(1),
174            Constraint::Length(1),
175        ]));
176        let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177            Constraint::Fill(1),
178            Constraint::Length(1),
179        ]));
180
181        self.layout = Some(LayoutState {
182            content,
183            vertical_bar,
184            horizontal_bar,
185        });
186
187        // Keep offsets valid when the terminal is resized.
188        let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189        self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190        self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192        let horizontal_lengths = ScrollLengths {
193            content_len: h_metrics.content_len(),
194            viewport_len: h_metrics.viewport_len(),
195        };
196        let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197        let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198        let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199        let horizontal = ScrollBar::horizontal(horizontal_lengths)
200            .arrows(ScrollBarArrows::Both)
201            .offset(self.horizontal_offset)
202            .scroll_step(SUBCELL)
203            .track_style(track_style)
204            .thumb_style(thumb_style)
205            .arrow_style(arrow_style);
206        let vertical_lengths = ScrollLengths {
207            content_len: v_metrics.content_len(),
208            viewport_len: v_metrics.viewport_len(),
209        };
210        let vertical = ScrollBar::vertical(vertical_lengths)
211            .arrows(ScrollBarArrows::Both)
212            .offset(self.vertical_offset)
213            .scroll_step(SUBCELL)
214            .track_style(track_style)
215            .thumb_style(thumb_style)
216            .arrow_style(arrow_style);
217
218        frame.render_widget(&horizontal, horizontal_bar);
219        frame.render_widget(&vertical, vertical_bar);
220    }
221
222    /// Handles keyboard and mouse events, updating offsets as needed.
223    fn handle_events(&mut self) -> Result<()> {
224        match event::read()? {
225            Event::Key(key) => {
226                if key.is_press() {
227                    self.handle_key_event(key.code);
228                }
229            }
230            Event::Mouse(event) => {
231                self.handle_mouse_event(event);
232            }
233            _ => {}
234        }
235        Ok(())
236    }
237
238    /// Handles keyboard input, updating offsets or exiting as needed.
239    fn handle_key_event(&mut self, code: KeyCode) {
240        match code {
241            KeyCode::Char('q') | KeyCode::Esc => self.state = AppState::Quit,
242            KeyCode::Up | KeyCode::Char('k') => self.handle_key_scroll(0, -(KEY_STEP as isize)),
243            KeyCode::Down | KeyCode::Char('j') => self.handle_key_scroll(0, KEY_STEP as isize),
244            KeyCode::Left | KeyCode::Char('h') => self.handle_key_scroll(-(KEY_STEP as isize), 0),
245            KeyCode::Right | KeyCode::Char('l') => self.handle_key_scroll(KEY_STEP as isize, 0),
246            _ => {}
247        }
248    }
249
250    /// Applies a keyboard delta to the scrollbar offsets.
251    fn handle_key_scroll(&mut self, dx: isize, dy: isize) {
252        let Some(layout) = self.layout else {
253            return;
254        };
255        let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
256        self.horizontal_offset =
257            Self::apply_delta(self.horizontal_offset, dx, h_metrics.max_offset());
258        self.vertical_offset = Self::apply_delta(self.vertical_offset, dy, v_metrics.max_offset());
259    }
260
261    /// Handles crossterm mouse events using the scrollbar helpers.
262    fn handle_mouse_event(&mut self, event: event::MouseEvent) {
263        let Some(layout) = self.layout else {
264            return;
265        };
266        let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
267        let horizontal = self.horizontal_scrollbar(h_metrics);
268        let vertical = self.vertical_scrollbar(v_metrics);
269
270        if let Some(command) = horizontal.handle_mouse_event(
271            layout.horizontal_bar,
272            event,
273            &mut self.horizontal_interaction,
274        ) {
275            self.apply_command(command, true);
276        }
277        if let Some(command) =
278            vertical.handle_mouse_event(layout.vertical_bar, event, &mut self.vertical_interaction)
279        {
280            self.apply_command(command, false);
281        }
282    }
283
284    /// Applies a scroll command to the current axis offset.
285    fn apply_command(&mut self, command: ScrollCommand, is_horizontal: bool) {
286        let ScrollCommand::SetOffset(offset) = command;
287        if is_horizontal {
288            self.horizontal_offset = offset;
289        } else {
290            self.vertical_offset = offset;
291        }
292    }
293
294    /// Builds a horizontal scrollbar from the current metrics.
295    fn horizontal_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
296        let lengths = ScrollLengths {
297            content_len: metrics.content_len(),
298            viewport_len: metrics.viewport_len(),
299        };
300        ScrollBar::horizontal(lengths)
301            .arrows(ScrollBarArrows::Both)
302            .offset(self.horizontal_offset)
303            .scroll_step(SUBCELL)
304    }
305
306    /// Builds a vertical scrollbar from the current metrics.
307    fn vertical_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
308        let lengths = ScrollLengths {
309            content_len: metrics.content_len(),
310            viewport_len: metrics.viewport_len(),
311        };
312        ScrollBar::vertical(lengths)
313            .arrows(ScrollBarArrows::Both)
314            .offset(self.vertical_offset)
315            .scroll_step(SUBCELL)
316    }
Source

pub const fn track_click_behavior(self, behavior: TrackClickBehavior) -> Self

Sets behavior for clicks on the track outside the thumb.

Use TrackClickBehavior::Page for classic page-up/down behavior, or TrackClickBehavior::JumpToClick to move the thumb toward the click.

This does not affect clicks on the thumb or arrow endcaps.

use tui_scrollbar::{ScrollBar, ScrollLengths, TrackClickBehavior};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 40,
};
let scrollbar =
    ScrollBar::vertical(lengths).track_click_behavior(TrackClickBehavior::JumpToClick);
Source

pub fn scroll_step(self, step: usize) -> Self

Sets the scroll step used for wheel events.

The wheel delta is multiplied by this value (in your logical units) and then clamped. A step of 0 is normalized to 1.

use tui_scrollbar::{ScrollBar, ScrollLengths};

let lengths = ScrollLengths {
    content_len: 120,
    viewport_len: 40,
};
let scrollbar = ScrollBar::vertical(lengths).scroll_step(8);
Examples found in repository?
examples/scrollbar_mouse.rs (line 202)
123    fn render(&mut self, frame: &mut ratatui::Frame) {
124        let area = frame.area();
125        if area.width < 2 || area.height < 2 {
126            return;
127        }
128
129        let title = "tui-scrollbar - mouse scroll demo";
130        let block = Block::new()
131            .borders(Borders::TOP)
132            .border_style(Style::new().fg(TITLE_FG).bg(TITLE_BG))
133            .style(Style::new().fg(BLOCK_FG).bg(BLOCK_BG))
134            .title(
135                Line::from(title)
136                    .centered()
137                    .fg(TITLE_FG)
138                    .bg(TITLE_BG)
139                    .bold(),
140            );
141        frame.render_widget(&block, area);
142
143        let content_area = Rect {
144            y: area.y.saturating_add(1),
145            height: area.height.saturating_sub(1),
146            ..area
147        };
148        let help = "Arrows: move | Wheel: scroll | Drag: thumb | q/Esc: quit";
149        let help_area = Rect {
150            x: content_area.x.saturating_add(1),
151            y: content_area.y,
152            width: content_area.width.saturating_sub(1),
153            height: 1,
154        };
155        if help_area.width > 0 {
156            frame.render_widget(
157                Paragraph::new(help).style(Style::new().fg(TITLE_FG)),
158                help_area,
159            );
160        }
161        let content_area = Rect {
162            y: content_area.y.saturating_add(1),
163            height: content_area.height.saturating_sub(1),
164            ..content_area
165        };
166
167        // Split out the bottom row and right column for the scrollbars.
168        let [content_row, bar_row] = content_area.layout(&Layout::vertical([
169            Constraint::Fill(1),
170            Constraint::Length(1),
171        ]));
172        let [content, vertical_bar] = content_row.layout(&Layout::horizontal([
173            Constraint::Fill(1),
174            Constraint::Length(1),
175        ]));
176        let [horizontal_bar, _corner] = bar_row.layout(&Layout::horizontal([
177            Constraint::Fill(1),
178            Constraint::Length(1),
179        ]));
180
181        self.layout = Some(LayoutState {
182            content,
183            vertical_bar,
184            horizontal_bar,
185        });
186
187        // Keep offsets valid when the terminal is resized.
188        let (h_metrics, v_metrics) = self.metrics_for_layout(content);
189        self.horizontal_offset = self.horizontal_offset.min(h_metrics.max_offset());
190        self.vertical_offset = self.vertical_offset.min(v_metrics.max_offset());
191
192        let horizontal_lengths = ScrollLengths {
193            content_len: h_metrics.content_len(),
194            viewport_len: h_metrics.viewport_len(),
195        };
196        let track_style = Style::new().bg(SCROLLBAR_TRACK_BG);
197        let thumb_style = Style::new().fg(SCROLLBAR_THUMB_FG).bg(SCROLLBAR_THUMB_BG);
198        let arrow_style = Style::new().fg(SCROLLBAR_ARROW_FG).bg(SCROLLBAR_TRACK_BG);
199        let horizontal = ScrollBar::horizontal(horizontal_lengths)
200            .arrows(ScrollBarArrows::Both)
201            .offset(self.horizontal_offset)
202            .scroll_step(SUBCELL)
203            .track_style(track_style)
204            .thumb_style(thumb_style)
205            .arrow_style(arrow_style);
206        let vertical_lengths = ScrollLengths {
207            content_len: v_metrics.content_len(),
208            viewport_len: v_metrics.viewport_len(),
209        };
210        let vertical = ScrollBar::vertical(vertical_lengths)
211            .arrows(ScrollBarArrows::Both)
212            .offset(self.vertical_offset)
213            .scroll_step(SUBCELL)
214            .track_style(track_style)
215            .thumb_style(thumb_style)
216            .arrow_style(arrow_style);
217
218        frame.render_widget(&horizontal, horizontal_bar);
219        frame.render_widget(&vertical, vertical_bar);
220    }
221
222    /// Handles keyboard and mouse events, updating offsets as needed.
223    fn handle_events(&mut self) -> Result<()> {
224        match event::read()? {
225            Event::Key(key) => {
226                if key.is_press() {
227                    self.handle_key_event(key.code);
228                }
229            }
230            Event::Mouse(event) => {
231                self.handle_mouse_event(event);
232            }
233            _ => {}
234        }
235        Ok(())
236    }
237
238    /// Handles keyboard input, updating offsets or exiting as needed.
239    fn handle_key_event(&mut self, code: KeyCode) {
240        match code {
241            KeyCode::Char('q') | KeyCode::Esc => self.state = AppState::Quit,
242            KeyCode::Up | KeyCode::Char('k') => self.handle_key_scroll(0, -(KEY_STEP as isize)),
243            KeyCode::Down | KeyCode::Char('j') => self.handle_key_scroll(0, KEY_STEP as isize),
244            KeyCode::Left | KeyCode::Char('h') => self.handle_key_scroll(-(KEY_STEP as isize), 0),
245            KeyCode::Right | KeyCode::Char('l') => self.handle_key_scroll(KEY_STEP as isize, 0),
246            _ => {}
247        }
248    }
249
250    /// Applies a keyboard delta to the scrollbar offsets.
251    fn handle_key_scroll(&mut self, dx: isize, dy: isize) {
252        let Some(layout) = self.layout else {
253            return;
254        };
255        let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
256        self.horizontal_offset =
257            Self::apply_delta(self.horizontal_offset, dx, h_metrics.max_offset());
258        self.vertical_offset = Self::apply_delta(self.vertical_offset, dy, v_metrics.max_offset());
259    }
260
261    /// Handles crossterm mouse events using the scrollbar helpers.
262    fn handle_mouse_event(&mut self, event: event::MouseEvent) {
263        let Some(layout) = self.layout else {
264            return;
265        };
266        let (h_metrics, v_metrics) = self.metrics_for_layout(layout.content);
267        let horizontal = self.horizontal_scrollbar(h_metrics);
268        let vertical = self.vertical_scrollbar(v_metrics);
269
270        if let Some(command) = horizontal.handle_mouse_event(
271            layout.horizontal_bar,
272            event,
273            &mut self.horizontal_interaction,
274        ) {
275            self.apply_command(command, true);
276        }
277        if let Some(command) =
278            vertical.handle_mouse_event(layout.vertical_bar, event, &mut self.vertical_interaction)
279        {
280            self.apply_command(command, false);
281        }
282    }
283
284    /// Applies a scroll command to the current axis offset.
285    fn apply_command(&mut self, command: ScrollCommand, is_horizontal: bool) {
286        let ScrollCommand::SetOffset(offset) = command;
287        if is_horizontal {
288            self.horizontal_offset = offset;
289        } else {
290            self.vertical_offset = offset;
291        }
292    }
293
294    /// Builds a horizontal scrollbar from the current metrics.
295    fn horizontal_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
296        let lengths = ScrollLengths {
297            content_len: metrics.content_len(),
298            viewport_len: metrics.viewport_len(),
299        };
300        ScrollBar::horizontal(lengths)
301            .arrows(ScrollBarArrows::Both)
302            .offset(self.horizontal_offset)
303            .scroll_step(SUBCELL)
304    }
305
306    /// Builds a vertical scrollbar from the current metrics.
307    fn vertical_scrollbar(&self, metrics: ScrollMetrics) -> ScrollBar {
308        let lengths = ScrollLengths {
309            content_len: metrics.content_len(),
310            viewport_len: metrics.viewport_len(),
311        };
312        ScrollBar::vertical(lengths)
313            .arrows(ScrollBarArrows::Both)
314            .offset(self.vertical_offset)
315            .scroll_step(SUBCELL)
316    }

Trait Implementations§

Source§

impl Clone for ScrollBar

Source§

fn clone(&self) -> ScrollBar

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ScrollBar

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for ScrollBar

Source§

impl PartialEq for ScrollBar

Source§

fn eq(&self, other: &ScrollBar) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl StructuralPartialEq for ScrollBar

Source§

impl Widget for &ScrollBar

Source§

fn render(self, area: Rect, buf: &mut Buffer)

Draws the current state of the widget in the given buffer. That is the only method required to implement a custom widget.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.