| Crates.io | tui-scrollbar |
| lib.rs | tui-scrollbar |
| version | 0.2.2 |
| created_at | 2025-12-28 03:27:57.705784+00 |
| updated_at | 2026-01-05 06:26:40.320263+00 |
| description | A Ratatui scrollbar widget with fractional thumb rendering |
| homepage | |
| repository | https://github.com/joshka/tui-widgets |
| max_upload_size | |
| id | 2008180 |
| size | 194,607 |
Smooth, fractional scrollbars for Ratatui. Part of the tui-widgets suite by Joshka.

GitHub Repository · API Docs · Examples · Changelog · Contributing · Crate source
Use this crate when you want scrollbars that communicate position and size more precisely than
full-cell glyphs. The widget renders into a Buffer for a given Rect and stays reusable
by implementing Widget for &ScrollBar.
Widget for &ScrollBar with external state.ScrollMetrics] exposes pure geometry for testing and hit testing.Ratatui's built-in scrollbar favors simple full-cell glyphs and a stateful widget workflow. This crate chooses fractional glyphs for more precise thumbs, keeps rendering stateless, and exposes a small interaction API plus pure metrics so apps can control behavior explicitly.
cargo add tui-scrollbar
This example renders a vertical [ScrollBar] into a Buffer using a fixed track size and
offset. Use it as a minimal template when you just need a thumb and track on screen.
If you prefer named arguments, use [ScrollLengths].
use ratatui_core::buffer::Buffer;
use ratatui_core::layout::Rect;
use ratatui_core::widgets::Widget;
use tui_scrollbar::{ScrollBar, ScrollBarArrows, ScrollLengths};
let area = Rect::new(0, 0, 1, 6);
let lengths = ScrollLengths {
content_len: 120,
viewport_len: 30,
};
let scrollbar = ScrollBar::vertical(lengths)
.arrows(ScrollBarArrows::Both)
.offset(45);
let mut buffer = Buffer::empty(area);
scrollbar.render(area, &mut buffer);
The scrollbar works in three pieces:
content_len, viewport_len, and offset (lengths along the scroll axis).ScrollMetrics] converts those values into a thumb position and size.ScrollBar] renders the track + thumb using fractional glyphs.Most apps update offset in response to input events and re-render each frame.
content_len, viewport_len, and offset are measured in logical units along the scroll
axis. For many apps, those units are items or lines. The ratio between viewport_len and
content_len is what matters, so any consistent unit works.
Zero lengths are treated as 1.
This example shows how to reserve a column for a vertical [ScrollBar] alongside your content.
Use the same pattern for a horizontal [ScrollBar] by splitting rows instead of columns.
use ratatui_core::buffer::Buffer;
use ratatui_core::layout::{Constraint, Layout, Rect};
use ratatui_core::widgets::Widget;
use tui_scrollbar::{ScrollBar, ScrollLengths};
let area = Rect::new(0, 0, 12, 6);
let [content_area, bar_area] = area.layout(&Layout::horizontal([
Constraint::Fill(1),
Constraint::Length(1),
]));
let lengths = ScrollLengths {
content_len: 400,
viewport_len: 80,
};
let scrollbar = ScrollBar::vertical(lengths).offset(0);
let mut buffer = Buffer::empty(area);
scrollbar.render(bar_area, &mut buffer);
This pattern assumes you have enabled mouse capture in your terminal backend and have the
scrollbar Rect (bar_area) from your layout each frame. Keep a [ScrollBarInteraction] in
your app state so drag operations persist across draws. Mouse events are handled via
[ScrollBar::handle_mouse_event], which returns a [ScrollCommand] to apply.
use ratatui_core::layout::Rect;
use tui_scrollbar::{ScrollBar, ScrollBarInteraction, ScrollCommand, ScrollLengths};
let bar_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;
if let Event::Mouse(event) = event::read()? {
if let Some(ScrollCommand::SetOffset(next)) =
scrollbar.handle_mouse_event(bar_area, event, &mut interaction)
{
offset = next;
}
}
This example shows how to compute thumb geometry without rendering via [ScrollMetrics]. It's
useful for testing, hit testing, or when you want to inspect thumb sizing directly.
use tui_scrollbar::{ScrollLengths, ScrollMetrics, SUBCELL};
let track_cells = 12;
let viewport_len = track_cells * SUBCELL;
let content_len = viewport_len * 6;
let lengths = ScrollLengths {
content_len,
viewport_len,
};
let metrics = ScrollMetrics::new(lengths, 0, track_cells as u16);
assert!(metrics.thumb_len() >= SUBCELL);
The default glyphs include Symbols for Legacy Computing so the thumb can render upper/right
partial fills that are missing from the standard block set. Use [GlyphSet] when you want to
switch to a glyph set that avoids legacy symbols.
use tui_scrollbar::{GlyphSet, ScrollBar, ScrollLengths};
let lengths = ScrollLengths {
content_len: 10,
viewport_len: 5,
};
let scrollbar = ScrollBar::vertical(lengths).glyph_set(GlyphSet::unicode());
ScrollBar]: renders vertical or horizontal scrollbars with fractional thumbs.ScrollBarInteraction]: drag capture state for pointer interaction.ScrollMetrics]: pure math for thumb sizing and hit testing.GlyphSet]: glyph selection for track and thumb rendering.ScrollBarArrows]: arrow endcap configuration.ScrollBarOrientation], [ScrollBarArrows], [TrackClickBehavior]ScrollEvent], [ScrollCommand]PointerEvent], [PointerEventKind], [PointerButton]ScrollWheel], [ScrollAxis]crossterm: enables crossterm mouse events (latest supported version, currently
crossterm 0.29).crossterm_0_28: enables crossterm mouse events using crossterm 0.28.crossterm_0_29: enables crossterm mouse events using crossterm 0.29.When multiple crossterm versions are enabled, the latest one is used.
The selected version is re-exported as tui_scrollbar::crossterm.
ScrollBarArrows].GlyphSet] hides the track using spaces; use [GlyphSet::box_drawing] or
[GlyphSet::unicode] for a visible track.GlyphSet::unicode] if you need only standard Unicode block elements.For the full suite of widgets, see tui-widgets.
Copyright (c) Josh McKinney
This project is licensed under either of:
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
See CONTRIBUTING.md.