//! # [Ratatui] Scrollbar example
//! The latest version of this example is available in the [examples] folder in the repository.
//! Please note that the examples are designed to be run against the `main` branch of the Github
//! repository. This means that you may not be able to compile with the latest release version on
//! crates.io, or the one that you have installed locally.
//! See the [examples readme] for more information on finding examples that match the version of the
//! library you are using.
//! [Ratatui]: https://github.com/ratatui/ratatui
//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
#![warn(clippy::pedantic)]
use std::time::{Duration, Instant};
crossterm::event::{self, Event, KeyCode},
layout::{Alignment, Constraint, Layout, Margin},
style::{Color, Style, Stylize},
text::{Line, Masked, Span},
widgets::{Block, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState},
pub vertical_scroll_state: ScrollbarState,
pub horizontal_scroll_state: ScrollbarState,
pub vertical_scroll: usize,
pub horizontal_scroll: usize,
fn main() -> Result<()> {
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
let tick_rate = Duration::from_millis(250);
let mut last_tick = Instant::now();
terminal.draw(|frame| self.draw(frame))?;
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
if event::poll(timeout)? {
if let Event::Key(key) = event::read()? {
KeyCode::Char('q') => return Ok(()),
KeyCode::Char('j') | KeyCode::Down => {
self.vertical_scroll = self.vertical_scroll.saturating_add(1);
self.vertical_scroll_state =
self.vertical_scroll_state.position(self.vertical_scroll);
KeyCode::Char('k') | KeyCode::Up => {
self.vertical_scroll = self.vertical_scroll.saturating_sub(1);
self.vertical_scroll_state =
self.vertical_scroll_state.position(self.vertical_scroll);
KeyCode::Char('h') | KeyCode::Left => {
self.horizontal_scroll = self.horizontal_scroll.saturating_sub(1);
self.horizontal_scroll_state = self
.position(self.horizontal_scroll);
KeyCode::Char('l') | KeyCode::Right => {
self.horizontal_scroll = self.horizontal_scroll.saturating_add(1);
self.horizontal_scroll_state = self
.position(self.horizontal_scroll);
if last_tick.elapsed() >= tick_rate {
last_tick = Instant::now();
#[allow(clippy::too_many_lines, clippy::cast_possible_truncation)]
fn draw(&mut self, frame: &mut Frame) {
// Words made "loooong" to demonstrate line breaking.
"Veeeeeeeeeeeeeeeery loooooooooooooooooong striiiiiiiiiiiiiiiiiiiiiiiiiing. ";
let mut long_line = s.repeat(usize::from(area.width) / s.len() + 4);
let chunks = Layout::vertical([
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
Line::from("This is a line "),
Line::from("This is a line ".red()),
Line::from("This is a line".on_dark_gray()),
Line::from("This is a longer line".crossed_out()),
Line::from(long_line.clone()),
Line::from("This is a line".reset()),
Span::raw("Masked text: "),
Span::styled(Masked::new("password", '*'), Style::new().fg(Color::Red)),
Line::from("This is a line "),
Line::from("This is a line ".red()),
Line::from("This is a line".on_dark_gray()),
Line::from("This is a longer line".crossed_out()),
Line::from(long_line.clone()),
Line::from("This is a line".reset()),
Span::raw("Masked text: "),
Span::styled(Masked::new("password", '*'), Style::new().fg(Color::Red)),
self.vertical_scroll_state = self.vertical_scroll_state.content_length(text.len());
self.horizontal_scroll_state = self.horizontal_scroll_state.content_length(long_line.len());
let create_block = |title: &'static str| Block::bordered().gray().title(title.bold());
.title_alignment(Alignment::Center)
.title("Use h j k l or ◄ ▲ ▼ ► to scroll ".bold());
frame.render_widget(title, chunks[0]);
let paragraph = Paragraph::new(text.clone())
.block(create_block("Vertical scrollbar with arrows"))
.scroll((self.vertical_scroll as u16, 0));
frame.render_widget(paragraph, chunks[1]);
frame.render_stateful_widget(
Scrollbar::new(ScrollbarOrientation::VerticalRight)
&mut self.vertical_scroll_state,
let paragraph = Paragraph::new(text.clone())
"Vertical scrollbar without arrows, without track symbol and mirrored",
.scroll((self.vertical_scroll as u16, 0));
frame.render_widget(paragraph, chunks[2]);
frame.render_stateful_widget(
Scrollbar::new(ScrollbarOrientation::VerticalLeft)
.symbols(scrollbar::VERTICAL)
&mut self.vertical_scroll_state,
let paragraph = Paragraph::new(text.clone())
"Horizontal scrollbar with only begin arrow & custom thumb symbol",
.scroll((0, self.horizontal_scroll as u16));
frame.render_widget(paragraph, chunks[3]);
frame.render_stateful_widget(
Scrollbar::new(ScrollbarOrientation::HorizontalBottom)
&mut self.horizontal_scroll_state,
let paragraph = Paragraph::new(text.clone())
"Horizontal scrollbar without arrows & custom thumb and track symbol",
.scroll((0, self.horizontal_scroll as u16));
frame.render_widget(paragraph, chunks[4]);
frame.render_stateful_widget(
Scrollbar::new(ScrollbarOrientation::HorizontalBottom)
.track_symbol(Some("─")),
&mut self.horizontal_scroll_state,