29 releases

new 0.15.0 Jan 11, 2026
0.14.0 Dec 27, 2025
0.13.3 Dec 8, 2025
0.13.2 Dec 28, 2024
0.2.2 May 3, 2023

#62 in Command-line interface

Download history 848/week @ 2025-09-21 1654/week @ 2025-09-28 1393/week @ 2025-10-05 1589/week @ 2025-10-12 1496/week @ 2025-10-19 1190/week @ 2025-10-26 812/week @ 2025-11-02 1507/week @ 2025-11-09 1399/week @ 2025-11-16 1286/week @ 2025-11-23 1396/week @ 2025-11-30 1887/week @ 2025-12-07 1832/week @ 2025-12-14 848/week @ 2025-12-21 581/week @ 2025-12-28 1395/week @ 2026-01-04

4,784 downloads per month
Used in 19 crates (18 directly)

MIT license

3.5MB
1.5K SLoC

A versatile widget list for Ratatui

Crate Badge Continuous Integration Deps Status License Badge

This crate provides a stateful widget ListView implementation for Ratatui. The associated ListState, offers functionalities such as navigating to the next and previous items. The list view support both horizontal and vertical scrolling.

Configuration

The ListView can be customized with the following options:

Example

use ratatui::prelude::*;
use tui_widget_list::{ListBuilder, ListState, ListView};

#[derive(Debug, Clone)]
pub struct ListItem {
    text: String,
    style: Style,
}

impl ListItem {
    pub fn new<T: Into<String>>(text: T) -> Self {
        Self {
            text: text.into(),
            style: Style::default(),
        }
    }
}

impl Widget for ListItem {
    fn render(self, area: Rect, buf: &mut Buffer) {
        Line::from(self.text).style(self.style).render(area, buf);
    }
}

pub struct App {
    state: ListState,
}

impl Widget for &mut App {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let builder = ListBuilder::new(|context| {
           let mut item = ListItem::new(&format!("Item {:0}", context.index));

           // Alternating styles
           if context.index % 2 == 0 {
               item.style = Style::default().bg(Color::Rgb(28, 28, 32));
           } else {
               item.style = Style::default().bg(Color::Rgb(0, 0, 0));
           }

           // Style the selected element
           if context.is_selected {
               item.style = Style::default()
                   .bg(Color::Rgb(255, 153, 0))
                   .fg(Color::Rgb(28, 28, 32));
           };

           // Return the size of the widget along the main axis.
           let main_axis_size = 1;

           (item, main_axis_size)
        });

        let item_count = 2;
        let list = ListView::new(builder, item_count);
        let state = &mut self.state;

        list.render(area, buf, state);
    }
}

Mouse handling

You can handle mouse clicks using ListState via hit_test:

match event::read()? {
    Event::Mouse(MouseEvent {
        kind: MouseEventKind::Down(MouseButton::Left),
        column, row, ..
    }) => {
        if let Some(index) = hit_test(&state, column, row) {
            state.select(Some(index));
        }
    }
    Event::Mouse(MouseEvent { kind: MouseEventKind::ScrollUp, .. }) => {
        state.previous();
    }
    Event::Mouse(MouseEvent { kind: MouseEventKind::ScrollDown, .. }) => {
        state.next();
    }
    _ => {}
}

For more examples see tui-widget-list.

Documentation

docs.rs

Demo

Infinite scrolling, scroll padding, horizontal scrolling

License: MIT

Dependencies

~7MB
~117K SLoC