Build terminal user interfaces and dashboards using Rust



Build Status Crate Status Docs Status

Demo cast under Linux Termite with Inconsolata font 12pt

tui-rs is a Rust library to build rich terminal user interfaces and dashboards. It is heavily inspired by the Javascript library blessed-contrib and the Go library termui.

The library itself supports four different backends to draw to the terminal. You can either choose from:

However, some features may only be available in one of the four.

The library is based on the principle of immediate rendering with intermediate buffers. This means that at each new frame you should build all widgets that are supposed to be part of the UI. While providing a great flexibility for rich and interactive UI, this may introduce overhead for highly dynamic content. So, the implementation try to minimize the number of ansi escapes sequences generated to draw the updated UI. In practice, given the speed of Rust the overhead rather comes from the terminal emulator than the library itself.

Moreover, the library does not provide any input handling nor any event system and you may rely on the previously cited libraries to achieve such features.

Rust version requirements

Since version 0.10.0, tui requires rustc version 1.44.0 or greater.



The demo shown in the gif can be run with all available backends (examples/* files). For example to see the termion version one could run:

cargo run --example termion_demo --release -- --tick-rate 200

where tick-rate is the UI refresh rate in ms.

The UI code is in examples/demo/ while the application state is in examples/demo/

Beware that the termion_demo only works on Unix platforms. If you are a Windows user, you can see the same demo using the crossterm backend with the following command:

cargo run --example crossterm_demo --no-default-features --features="crossterm" --release -- --tick-rate 200

If the user interface contains glyphs that are not displayed correctly by your terminal, you may want to run the demo without those symbols:

cargo run --example crossterm_demo --no-default-features --features="crossterm" --release -- --tick-rate 200 --enhanced-graphics false


The library comes with the following list of widgets:

Click on each item to see the source of the example. Run the examples with with cargo (e.g. to run the demo cargo run --example demo), and quit by pressing q.

You can run all examples by running make run-examples.

Third-party widgets

Apps using tui


You might want to checkout Cursive for an alternative solution to build text user interfaces in Rust.



  • Table widget panics when table doesnt fit screen

    Table widget panics when table doesnt fit screen

    Describe the bug The table widget is panicking when it doesn't for the screen

    Here is the stack trace thrown at

    thread '<unnamed>' panicked at 'Trying to access position outside the buffer: x=189, y=16, area=Rect { x: 0, y: 0, width: 189, height: 40 }', /home/deepu/.cargo/registry/src/
       0: kdash::panic_hook
                 at src/
       1: kdash::main::{{closure}}::{{closure}}
                 at src/
       2: std::panicking::rust_panic_with_hook
                 at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/
       3: std::panicking::begin_panic_handler::{{closure}}
                 at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/
       4: std::sys_common::backtrace::__rust_end_short_backtrace
                 at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/sys_common/
       5: rust_begin_unwind
                 at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/
       6: std::panicking::begin_panic_fmt
                 at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/
       7: tui::buffer::Buffer::index_of
                 at /home/deepu/.cargo/registry/src/
       8: tui::buffer::Buffer::get_mut
                 at /home/deepu/.cargo/registry/src/
       9: tui::buffer::Buffer::set_style
                 at /home/deepu/.cargo/registry/src/
      10: tui::widgets::table::render_cell
                 at /home/deepu/.cargo/registry/src/
      11: <tui::widgets::table::Table as tui::widgets::StatefulWidget>::render
                 at /home/deepu/.cargo/registry/src/
      12: tui::terminal::Frame<B>::render_stateful_widget
                 at /home/deepu/.cargo/registry/src/
      13: kdash::ui::overview::draw_nodes
                 at src/ui/
      14: kdash::ui::overview::draw_active_context_tabs
                 at src/ui/
      15: kdash::ui::overview::draw_overview
                 at src/ui/
      16: kdash::ui::draw
                 at src/ui/
      17: kdash::start_ui::{{closure}}::{{closure}}
                 at src/
      18: tui::terminal::Terminal<B>::draw
                 at /home/deepu/.cargo/registry/src/
      19: kdash::start_ui::{{closure}}
                 at src/
      20: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
                 at /home/deepu/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/future/
      21: kdash::main::{{closure}}
                 at src/
      22: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
                 at /home/deepu/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/future/
      23: tokio::park::thread::CachedParkThread::block_on::{{closure}}
                 at /home/deepu/.cargo/registry/src/
      24: tokio::coop::with_budget::{{closure}}
                 at /home/deepu/.cargo/registry/src/
      25: std::thread::local::LocalKey<T>::try_with
                 at /home/deepu/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/
      26: std::thread::local::LocalKey<T>::with
                 at /home/deepu/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/
      27: tokio::coop::with_budget
                 at /home/deepu/.cargo/registry/src/
                 at /home/deepu/.cargo/registry/src/
                 at /home/deepu/.cargo/registry/src/
      28: tokio::runtime::enter::Enter::block_on
                 at /home/deepu/.cargo/registry/src/
      29: tokio::runtime::thread_pool::ThreadPool::block_on
                 at /home/deepu/.cargo/registry/src/
      30: tokio::runtime::Runtime::block_on
                 at /home/deepu/.cargo/registry/src/
      31: kdash::main
                 at src/
      32: core::ops::function::FnOnce::call_once
                 at /home/deepu/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/
      33: std::sys_common::backtrace::__rust_begin_short_backtrace
                 at /home/deepu/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys_common/
      34: std::rt::lang_start::{{closure}}
                 at /home/deepu/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/
      35: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
                 at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/ops/
                 at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/
                 at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/
                 at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/
                 at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/
      36: std::rt::lang_start
                 at /home/deepu/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/
      37: main
      38: __libc_start_main
      39: _start

    To Reproduce

    I couldnt reproduce this on a standalone setup as it seems to work fine with same data as in error case. Below are the table rows when error happened


    So i'm unable to pinpoint whats going wrong here

    Expected behavior Should not panick regardless of screen size

    Desktop (please complete the following information):

    • OS: Fedora 32. Kernel 5.11.10-100.fc32.x86_64
    • Terminal Emulatoron Yakuake, Tilix, Konsole and VSCode terminal
    • Font [Monospace]
    • Crate version [0.14]
    • Backend [crossterm]
    opened by deepu105 17
  • Control cursor position and visibility via Frame

    Control cursor position and visibility via Frame

    This implements the idea I outlined in

    can't Frame have a field like Option<(u16, u16)>, which is the cursor's desired position after drawing the frame? None means the cursor is hidden, Some with a position means the cursor is shown and is positioned at the given cell.

    The implementation deviates from the idea in two ways.

    First of all, Frame is moved into the drawing closure — there is no way for Terminal::draw() to examine frame's fields afterwards. This could be resolved in two ways:

    1. make Terminal::draw() require FnOnce(&mut Frame<B>). This is very clean, but it's a breaking change, so I decided against that.

    2. store that Option<(u16, u16)> in the Terminal itself, and make Frame just pass the calls through. That's a bit of a hack, because I don't think it's Terminal's responsibility to store this data; it's Frame's job to describe what has to be drawn and how.

    The second option is a non-breaking change, so I went with that.

    That decision led to the second deviation. Terminal already has an API for controlling the cursor: hide_cursor(), show_cursor(), set_cursor(). These calls have immediate effect on the terminal (modulo stdout buffering). This conflicts with the proposed API, and could be resolved in two ways:

    1. make the old API non-public. This doesn't seem right to me, since Terminal is supposed to be a low-level wrapper around the backend.

    2. make the new API opt-in. Up until the first call to Frame::set_desired_cursor_position(), calls to draw() don't affect cursor visibility, so the user is free to use Terminal::hide_cursor() and Terminal::show_cursor(). After the first call to the new API, however, the cursor position and visibility is always enforced after each Terminal::draw() call.

    The first option is not viable, so I went with the second one.

    I think that in the long term, tui-rs should change the signature of Terminal::draw(), to move all of this to Frame, and make Terminal a completely low-level thing again. If that's the plan, it might make sense to make the new API on Terminal non-public now, so people don't depend on what is going to be removed. @fdehau, I'm also okay with re-doing the PR to change the signature now, if you're ok with a breaking change.

    opened by Minoru 14
  • Text Style Background should affect whole Row/Cell in List and Table

    Text Style Background should affect whole Row/Cell in List and Table

    When using Text::styled in a List or Table, the background color only affects the text. I believe it should affect the whole Row/Cell in which the text is in.

    To Reproduce

    let splits_list = splits.iter().map(|(txt)| {
    let mut splits_list = List::new(splits_list)
    f.render(&mut splits_list, chunks[0]);


    Tui List BG


    • OS: Tested on [Linux Mint Cinnamon and Windows 10]
    • Terminal Emulator [Gnome Terminal, Power Shell and Cmd]
    • Font [Monospace and Consolas]
    • Crate version [0.8]
    • Backend [crossterm]
    opened by Ynscription 14
  • Preserve Indentation of Unwrapped Lines

    Preserve Indentation of Unwrapped Lines

    Thanks for the wonderful library! :heart:


    I ran into some unexpected behaviour recently when I was attempting to print a paragraph with some indentation in wrapping mode – TUI was stripping the leading whitespace from my lines.

    I've since discovered that this is an intentional behaviour, but that leaves me unable to print a paragraph like this:

    MW Word of the Day                                                            
       1) to diffuse through or penetrate something                               
       2) to spread or diffuse through                                            
       3) to pass through the pores or interstices of


    In this PR, I simply disabled this behaviour, as I feel it's more predicable.

    The above paragraph now wraps as follows:

    MW Word of the Day            
       1) to diffuse through or   
    penetrate something           
       2) to spread or diffuse    
       3) to pass through the     
    pores or interstices of


    Slightly funky? Perhaps, but much more predicable and arguably clearer than no indentation whatsoever.

    Comparing to the current master TUI branch:

    MW Word of the Day            
    1) to diffuse through or      
    penetrate something           
    2) to spread or diffuse       
    3) to pass through the pores  
    or interstices of 

    The lack of indentation makes it hard to tell that those are distinct "bullet points". The user can always trim strings in their application, but can't re-add the spacing TUI strips.


    I adapted one of the leading-space tests to test indentation is preserved, but the other two I removed. If you are curious, the output of the removed tests was:

    ---- widgets::reflow::test::line_composer_char_plus_lots_of_spaces stdout ----
    thread 'widgets::reflow::test::line_composer_char_plus_lots_of_spaces' panicked at 'assertion failed: `(left == right)`
      left: `["a", "                    ", "                    ", "       "]`,
     right: `["a", ""]`', src/widgets/
    ---- widgets::reflow::test::line_composer_lots_of_spaces stdout ----
    thread 'widgets::reflow::test::line_composer_lots_of_spaces' panicked at 'assertion failed: `(left == right)`
      left: `["                    ", "                    ", "                    ", "      "]`,
     right: `[""]`', src/widgets/

    Are a load of lines filled by nothing but spaces useful? Probably not, but at least this gives the user the choice to keep or remove them. This new behaviour is also somewhat more in line with the LineTruncator which leaves the trailing whitespace.

    opened by TheLostLambda 12
  • Implement style merging for highlight styles

    Implement style merging for highlight styles

    Previously the highlight style (even if not present) overrode the row styles for a table and list:


    With this change, if there is no highlight style defined, the selected item follows the style of the row:


    When a highlight style is defined, it is merged with the data style:


    This is now implemented on top of StyleDiff. The highlight_style and header_style are accepted through that. The basic style is still a normal Style as this should be the lowest priority style so there's nothing to patch that on top of.

    I also wish I could have added DiffStyled variants to Text and Row, but that would have been a compatibility break. Maybe it should be considered for the next semver-major release?

    There's also some overall improvements here in how styles get applied between highlight symbols, etc.

    opened by Rantanen 10
  • crossterm (re)size broken

    crossterm (re)size broken

    Describe the bug In order to prepare the pull request for the Clear-feature ( I pulled in the master and realized that crossterm terminal sizing seems broken: (see screenshot)

    The terminal seems to have a default size and not using up the actual terminal size. Also the resize events are not changing anything.

    To Reproduce In my case (macOS) I only need to run the Crossterm-demo:

    cargo run --example crossterm_demo --no-default-features --features="crossterm" --release -- -tick-rate 200

    Expected behavior A clear and concise description of what you expected to happen.

    Screenshots image

    Desktop (please complete the following information):

    • OS: macos
    • Terminal Emulator: iTerm2
    • Crate version: master
    • Backend: crossterm

    Additional context This was working as intended in the previous release

    opened by extrawurst 10
  • Intersted in crossterm implementation?

    Intersted in crossterm implementation?

    Hi, I am the writer of Crossterm cross-platform terminal manipulation library. In a short time, I will launch a new version of my crate which has than support for user input. It will then have all the features that termion has but then crossplatform.

    Interested? is there a place where I could contact you like discord or so?

    opened by TimonPost 10
  • Crossterm+Windows support

    Crossterm+Windows support

    Would love to try using tui, but when I run the demo it looks like it's not quite ready to use in Windows.

    Here's running the crossterm_demo in Windows:


    Is there a better way to get Windows support aside from crossterm? If not, are there any plans for making Windows supported?

    opened by jntrnr 9
  • Switch for rounded corners on widgets

    Switch for rounded corners on widgets

    I would like the option of building widgets with rounded corners given the unicode characters for this exist. I have tried to figure out how to implement that option but am having a difficult time figuring out the flow that leads to building a widget and where that option would be introduced. The below snippets may be confusing, so you can skip those and check if that is easier.

    In symbols::line I have this:

    pub mod line {
        pub const TOP_RIGHT: &str = "┐";
        pub const TOP_RIGHT_ROUNDED: &str = "╮";
        pub const VERTICAL: &str = "│";
        pub const HORIZONTAL: &str = "─";
        pub const TOP_LEFT: &str = "┌";
        pub const TOP_LEFT_ROUNDED: &str = "╭";
        pub const BOTTOM_RIGHT: &str = "┘";
        pub const BOTTOM_RIGHT_ROUNDED: &str = "╯";
        pub const BOTTOM_LEFT: &str = "└";
        pub const BOTTOM_LEFT_ROUNDED: &str = "╰";
        pub const VERTICAL_LEFT: &str = "┤";
        pub const VERTICAL_RIGHT: &str = "├";
        pub const HORIZONTAL_DOWN: &str = "┬";
        pub const HORIZONTAL_UP: &str = "┴";

    and for layout::Rect

    pub struct Rect {
        pub x: u16,
        pub y: u16,
        pub width: u16,
        pub height: u16,
        pub rounded_corners: bool,
    impl Default for Rect {
        fn default() -> Rect {
            Rect {
                x: 0,
                y: 0,
                width: 0,
                height: 0,
                rounded_corners: false,

    and in widgets::block:

            // Corners
            if self.borders.contains(Borders::LEFT | Borders::TOP) {
                let top_left = match area.rounded_corners {
                    true => line::TOP_LEFT_ROUNDED,
                    _ => line::TOP_LEFT,
            if self.borders.contains(Borders::RIGHT | Borders::TOP) {
                let top_right = match area.rounded_corners {
                    true => line::TOP_RIGHT_ROUNDED,
                    _ => line::TOP_RIGHT,
                buf.get_mut(area.right() - 1,
            if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
                let bottom_left = match area.rounded_corners {
                    true => line::BOTTOM_LEFT_ROUNDED,
                    _ => line::BOTTOM_LEFT,
                buf.get_mut(area.left(), area.bottom() - 1)
            if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
                let bottom_right = match area.rounded_corners {
                    true => line::BOTTOM_RIGHT_ROUNDED,
                    _ => line::BOTTOM_RIGHT,
                buf.get_mut(area.right() - 1, area.bottom() - 1)

    As I've dug into the rabbit hole of the build pattern I find that terminal_size() returns a rectangle (which seems a bit counter-intuitive to me as "size" implies just dimensions not shape). This seems to be the starting point so I've added a rounded_corners: bool field to the termion::TermionBackend struct and changed the size() method to:

        /// Return the size of the terminal
        fn size(&self) -> io::Result<Rect> {
            let terminal = termion::terminal_size()?;

    By then adding appropriate boolean parameters to the and files I have been able to compile the demo but the corners are still sharp not rounded. Any suggestions?

    opened by stevensonmt 9
  • Crossterm backend input is not working

    Crossterm backend input is not working

    I noticed, and I hear from some other people that the input of the crossterm backend is not working correctly, especially talking about the crossterm demo example. The keys seem not to be recorded. I can have a look at this in a few days, but if there are people that can do that as well it will be appreciated.

    opened by TimonPost 9
  • Using `Constraint::Min` in in `Table::widths` results in constantly changing column layout.

    Using `Constraint::Min` in in `Table::widths` results in constantly changing column layout.


    I added the following constraints to a new table. These aren't the final values I want, so they're just preliminary to see what it looks like:

                          tui::layout::Constraint::Max(10), // Commit date (truncates time by default)

    I end up seeing this: Peek 2021-06-19 20-16

    To Reproduce

    The latest commit /w this problem in my application is at

    You can also change examples/ with the following patch:

    index 846a55c..beda582 100644
    --- a/examples/
    +++ b/examples/
    @@ -118,7 +118,7 @@ fn main() -> Result<(), Box<dyn Error>> {
                     .highlight_symbol(">> ")
    -                    Constraint::Percentage(50),
    +                    Constraint::Min(10),
    diff --git a/src/ b/src/
    index 70a71d0..ae55981 100644
    --- a/src/
    +++ b/src/
    @@ -183,13 +183,13 @@ impl<'a> Span<'a> {
     impl<'a> From<String> for Span<'a> {
         fn from(s: String) -> Span<'a> {
    -        Span::raw(s)
    +        Span::raw(s.replace("\r", "").replace("\n", ""))
     impl<'a> From<&'a str> for Span<'a> {
         fn from(s: &'a str) -> Span<'a> {
    -        Span::raw(s)
    +        Span::raw(s.replace("\r", "").replace("\n", ""))

    And then:

    $ cargo run --example table

    Expected behavior

    Minimally, I expected the column widths to remain the same if the rows and no other state change. In this case I expect there to be 30 characters spent for the right-most columns and whatever is left is used by the left-most column (ie Constraint::Min(n) means it should be at least n wide but will also use any remaining width available).

    This might just be a docs snag, maybe Min means something else, but Constraint has no docs to explain otherwise.


    • OS: Linux
    • Terminal: gnome-terminal
    • Font: Ubuntu Mono Regular
    • Crate version: 0.14
    • Backend: crossterm
    opened by medwards 8
  • Update README: add hncli to list of applications made with tui-rs

    Update README: add hncli to list of applications made with tui-rs


    Add hncli, an Hacker News TUI reader, to the list of applications made with tui-rs. Thanks for the library, can help with maintenance if needed!

    Testing guidelines



    • [x] I have read the contributing guidelines.
    • [x] I have added relevant tests.
    • [x] I have documented all new additions.
    opened by pierreyoda 0
  • Add link to EDMA app in application list

    Add link to EDMA app in application list


    Add EDMA to tui-rs application list


    • [x ] I have read the contributing guidelines.
    • [x ] I have added relevant tests.
    • [x ] I have documented all new additions.
    opened by chungquantin 0
  • confused at how to compile and test the demo

    confused at how to compile and test the demo


    confusion in dependencies in the example and what how to test or compile each binary or all in one go its just confusing for begineer. For example,if new dev wanted to test the demo in the example,they will most probably try copy the folder out.but they need the cargo to build the deps,so they will try copy the deps but the cargo.toml needs example folder to exist,so they copy the whole example folder

    To Reproduce

    Expected behavior



    • OS:
    • Terminal Emulator:
    • Font:
    • Crate version:
    • Backend:

    Additional context

    opened by devdailytester 0
  • Update Add a new application to list of apps using tui-rs

    Update Add a new application to list of apps using tui-rs


    Add ruscode to list of projects using tui-rs. This crate had already published to

    Testing guidelines


    • [x] I have read the contributing guidelines.
    • [x] I have added relevant tests.
    • [x] I have documented all new additions.
    opened by MissterHao 0
  • Multiple selections in a Table

    Multiple selections in a Table


    Hello ! I just noticed that i can't select multiple items in a Table, only a single item is allowed.


    Implement code for allowing multiple selection of items in a Table. I see an open PR regarding this problem with List too, the work could be based on that with little modification.

    opened by kivimango 1
  • Empty lines (containing "") are hidden in List output


    I am building an alternative to the watch command watchbind. I am using the widget List with Listitems. I am wondering why the example code

    	let items = vec![
    		ListItem::new("line one"),
    		ListItem::new("line four"),
    	let list = List::new(items)
    	f.render_stateful_widget(list, f.size(), &mut events.state);

    does not render the second line, but the state obviously knows about it because I can scroll normally with j and k (which I programmed).

    To Reproduce

    Expected behavior

    All lines should be visible in the output, even the empty lines.


    2022-11-23-23:54:08 On the left side is the output of watchbind -- git -h, on the right side git -h. As you can see, the empty lines are omitted on the left, which uses the tui List widget.


    • OS: Linux
    • Terminal Emulator: kitty
    • Crate version: 0.19
    • Backend: crossterm

    Additional context

    opened by fritzrehde 1
  • v0.19.0(Aug 14, 2022)

  • v0.18.0(Apr 24, 2022)

  • v0.17.0(Jan 22, 2022)


    • Add option to widgets::List to repeat the hightlight symbol for each line of multi-line items (#533).
    • Add option to control the alignment of Axis labels in the Chart widget (#568).

    Breaking changes

    • The minimum supported rust version is now 1.56.1.

    New default backend and consolidated backend options (#553)

    • crossterm is now the default backend. If you are already using the crossterm backend, you can simplify your dependency specification in Cargo.toml:
    - tui = { version = "0.16", default-features = false, features = ["crossterm"] }
    + tui = "0.17"

    If you are using the termion backend, your Cargo is now a bit more verbose:

    - tui = "0.16"
    + tui = { version = "0.17", default-features = false, features = ["termion"] }

    crossterm has also been bumped to version 0.22.

    Because of their apparent low usage, curses and rustbox backends have been removed. If you are using one of them, you can import their last implementation in your own project:

    Canvas labels (#543)

    • Labels of the Canvas widget are now text::Spans. The signature of widgets::canvas::Context::print has thus been updated:
    - ctx.print(x, y, "Some text", Color::Yellow);
    + ctx.print(x, y, Span::styled("Some text", Style::default().fg(Color::Yellow)))
    Source code(tar.gz)
    Source code(zip)
  • v0.16.0(Aug 1, 2021)


    • Update crossterm to 0.20.
    • Add From<Cow<str>> implementation for text::Text (#471).
    • Add option to right or center align the title of a widgets::Block (#462).


    • Apply label style in widgets::Gauge and avoid panics because of overflows with long labels (#494).
    • Avoid panics because of overflows with long axis labels in widgets::Chart (#512).
    • Fix computation of column widths in widgets::Table (#514).
    • Fix panics because of invalid offset when input changes between two frames in widgets::List and widgets::Chart (#516).
    Source code(tar.gz)
    Source code(zip)
  • v0.15.0(May 2, 2021)


    • Update crossterm to 0.19.
    • Update rand to 0.8.
    • Add a read-only view of the terminal state after the draw call (#440).


    • Remove compile warning in TestBackend::assert_buffer (#466).
    Source code(tar.gz)
    Source code(zip)
  • v0.14.0(Jan 1, 2021)

    Breaking changes

    New API for the Table widget

    The Table widget got a lot of improvements that should make it easier to work with:

    • It should not longer panic when rendered on small areas.
    • Rows are now a collection of Cells, themselves wrapping a Text. This means you can style the entire Table, an entire Row, an entire Cell and rely on the styling capabilities of Text to get full control over the look of your Table.
    • Rows can have multiple lines.
    • The header is now optional and is just another Row always visible at the top.
    • Rows can have a bottom margin.
    • The header alignment is no longer off when an item is selected.

    Taking the example of the code in examples/demo/, this is what you may have to change:

         let failure_style = Style::default()
             .add_modifier(Modifier::RAPID_BLINK | Modifier::CROSSED_OUT);
    -    let header = ["Server", "Location", "Status"];
         let rows = app.servers.iter().map(|s| {
             let style = if s.status == "Up" {
             } else {
    -        Row::StyledData(vec![, s.location, s.status].into_iter(), style)
    +        Row::new(vec![, s.location, s.status]).style(style)
    -    let table = Table::new(header.iter(), rows)
    +    let table = Table::new(rows)
    +        .header(
    +            Row::new(vec!["Server", "Location", "Status"])
    +                .style(Style::default().fg(Color::Yellow))
    +                .bottom_margin(1),
    +        )
    -        .header_style(Style::default().fg(Color::Yellow))

    Here, we had to:

    • Change the way we construct Row which is no longer an enum but a struct. It accepts anything that can be converted to an iterator of things that can be converted to a Cell
    • The header is no longer a required parameter so we use Table::header to set it. Table::header_style has been removed since the style can be directly set using Row::style. In addition, we want to preserve the old margin between the header and the rest of the rows so we add a bottom margin to the header using Row::bottom_margin.

    You may want to look at the documentation of the different types to get a better understanding:


    • Fix handling of Non Breaking Space (NBSP) in wrapped text in Paragraph widget.


    • Add Style::reset to create a Style resetting all styling properties when applied.
    • Add an option to render the Gauge widget with unicode blocks.
    • Manage common project tasks with cargo-make rather than make for easier on-boarding.
    Source code(tar.gz)
    Source code(zip)
  • v0.13.0(Nov 14, 2020)


    • Add LineGauge widget which is a more compact variant of the existing Gauge.
    • Bump crossterm to 0.18


    • Take into account the borders of the Table widget when the widths of columns is controlled by Percentage and Ratio constraints.
    Source code(tar.gz)
    Source code(zip)
  • v0.12.0(Sep 27, 2020)


    • Make it easier to work with string with multiple lines in Text (#361).


    • Fix a style leak in Graph so components drawn on top of the plotted data (i.e legend and axis titles) are not affected by the style of the Datasets (#388).
    • Make sure BarChart shows bars with the max height only when the plotted data is actually equal to the max (#383).
    Source code(tar.gz)
    Source code(zip)
  • v0.11.0(Sep 27, 2020)


    • Add the dot character as a new type of canvas marker (#350).
    • Support more style modifiers on Windows (#368).


    • Clearing the terminal through Terminal::clear will cause the whole UI to be redrawn (#380).
    • Fix incorrect output when the first diff to draw is on the second cell of the terminal (#347).
    Source code(tar.gz)
    Source code(zip)
  • v0.10.0(Sep 27, 2020)

    Breaking changes

    Easier cursor management

    A new method has been added to Frame called set_cursor. It lets you specify where the cursor should be placed after the draw call. Furthermore like any other widgets, if you do not set a cursor position during a draw call, the cursor is automatically hidden.

    For example:

    fn draw_input(f: &mut Frame, app: &App) {
      if app.editing {
        let input_width = app.input.width() as u16;
        // The cursor will be placed just after the last character of the input
        f.set_cursor((input_width + 1, 0));
      } else {
        // We are no longer editing, the cursor does not have to be shown, set_cursor is not called and
        // thus automatically hidden.

    In order to make this possible, the draw closure takes in input &mut Frame instead of mut Frame.

    Advanced text styling

    It has been reported several times that the text styling capabilities were somewhat limited in many places of the crate. To solve the issue, this release includes a new set of text primitives that are now used by a majority of widgets to provide flexible text styling.

    Text is replaced by the following types:

    • Span: a string with a unique style.
    • Spans: a string with multiple styles.
    • Text: a multi-lines string with multiple styles.

    However, you do not always need this complexity so the crate provides From implementations to let you use simple strings as a default and switch to the previous primitives when you need additional styling capabilities.

    For example, the title of a Block can be set in the following ways:

    // A title with no styling
    Block::default().title("My title");
    // A yellow title
    Block::default().title(Span::styled("My title", Style::default().fg(Color::Yellow)));
    // A title where "My" is bold and "title" is a simple string
        Span::styled("My", Style::default().add_modifier(Modifier::BOLD)),
    • Buffer::set_spans and Buffer::set_span were added.
    • Paragraph::new expects an input that can be converted to a Text.
    • Block::title_style is deprecated.
    • Block::title expects a Spans.
    • Tabs expects a list of Spans.
    • Gauge custom label is now a Span.
    • Axis title and labels are Spans (as a consequence Chart no longer has generic bounds).

    Incremental styling

    Previously Style was used to represent an exhaustive set of style rules to be applied to an UI element. It implied that whenever you wanted to change even only one property you had to provide the complete style. For example, if you had a Block where you wanted to have a green background and a title in bold, you had to do the following:

    let style = Style::default().bg(Color::Green);
      .title("My title")
      // Here we reused the style otherwise the background color would have been reset

    In this new release, you may now write this as:

        // The style is not overidden anymore, we simply add new style rule for the title.
        .title(Span::styled("My title", Style::default().add_modifier(Modifier::BOLD)))

    In addition, the crate now provides a method patch to combine two styles into a new set of style rules:

    let style = Style::default().modifer(Modifier::BOLD);
    let style = style.patch(Style::default().add_modifier(Modifier::ITALIC));
    // style.modifer == Modifier::BOLD | Modifier::ITALIC, the modifier has been enriched not overidden
    • Style::modifier has been removed in favor of Style::add_modifier and Style::remove_modifier.
    • Buffer::set_style has been added. Buffer::set_background is deprecated.
    • BarChart::style no longer set the style of the bars. Use BarChart::bar_style in replacement.
    • Gauge::style no longer set the style of the gauge. Use Gauge::gauge_style in replacement.

    List with item on multiple lines

    The List widget has been refactored once again to support items with variable heights and complex styling.

    • List::new expects an input that can be converted to a Vec<ListItem> where ListItem is a wrapper around the item content to provide additional styling capabilities. ListItem contains a Text.
    • List::items has been removed.
    // Before
    let items = vec![
    // After
    let items = vec![

    See the examples for more advanced usages.

    More wrapping options

    Paragraph::wrap expects Wrap instead of bool to let users decided whether they want to trim whitespaces when the text is wrapped.

    // before
    // after
    Paragraph::new(text).wrap(Wrap { trim: true }) // to have the same behavior
    Paragraph::new(text).wrap(Wrap { trim: false }) // to use the new behavior

    Horizontal scrolling in paragraph

    You can now scroll horizontally in Paragraph. The argument of Paragraph::scroll has thus be changed from u16 to (u16, u16).


    Serialization of style

    You can now serialize and de-serialize Style using the optional serde feature.

    Source code(tar.gz)
    Source code(zip)
  • v0.9.0(Apr 14, 2020)


    • Introduce stateful widgets, i.e widgets that can take advantage of keeping some state around between two draw calls (#210 goes a bit more into the details).
    • Allow a Table row to be selected.
    // State initialization
    let mut state = TableState::default();
    // In the terminal.draw closure
    let header = ["Col1", "Col2", "Col"];
    let rows = [
      Row::Data(["Row11", "Row12", "Row13"].into_iter())
    let table = Table::new(header.into_iter(), rows.into_iter());
    f.render_stateful_widget(table, area, &mut state);
    // In response to some event:;
    • Add a way to choose the type of border used to draw a block. You can now choose from plain, rounded, double and thick lines.
    • Add a graph_type property on the Dataset of a Chart widget. By default it will be Scatter where the points are drawn as is. An other option is Line where a line will be draw between each consecutive points of the dataset.
    • Style methods are now const, allowing you to initialize const Style objects.
    • Improve control over whether the legend in the Chart widget is shown or not. You can now set custom constraints using Chart::hidden_legend_constraints.
    • Add Table::header_gap to add some space between the header and the first row.
    • Remove log from the dependencies
    • Add a way to use a restricted set of unicode symbols in several widgets to improve portability in exchange of a degraded output. (see BarChart::bar_set, Sparkline::bar_set and Canvas::marker). You can check how the --enhanced-graphics flag is used in the demos.

    Breaking Changes

    • Widget::render has been deleted. You should now use Frame::render_widget to render a widget on the corresponding Frame. This makes the Widget implementation totally decoupled from the Frame.
    // Before
    Block::default().render(&mut f, size);
    // After
    let block = Block::default();
    f.render_widget(block, size);
    • Widget::draw has been renamed to Widget::render and the signature has been updated to reflect that widgets are consumable objects. Thus the method takes self instead of &mut self.
    // Before
    impl Widget for MyWidget {
      fn draw(&mut self, area: Rect, buf: &mut Buffer) {
    /// After
    impl Widget for MyWidget {
      fn render(self, arera: Rect, buf: &mut Buffer) {
    • Widget::background has been replaced by Buffer::set_background
    // Before
    impl Widget for MyWidget {
      fn render(self, arera: Rect, buf: &mut Buffer) {
        self.background(area, buf,;
    // After
    impl Widget for MyWidget {
      fn render(self, arera: Rect, buf: &mut Buffer) {
    • Update the Shape trait for objects that can be draw on a Canvas widgets. Instead of returning an iterator over its points, a Shape is given a Painter object that provides a paint as well as a get_point method. This gives the Shape more information about the surface it will be drawn to. In particular, this change allows the Line shape to use a more precise and efficient drawing algorithm (Bresenham's line algorithm).
    • SelectableList has been deleted. You can now take advantage of the associated ListState of the List widget to select an item.
    // Before
    List::new(&["Item1", "Item2", "Item3"])
      .render(&mut f, area);
    // After
    // State initialization
    let mut state = ListState::default();
    // In the terminal.draw closure
    let list = List::new(&["Item1", "Item2", "Item3"]);
    f.render_stateful_widget(list, area, &mut state);
    // In response to some events;
    • widgets::Marker has been moved to symbols::Marker
    Source code(tar.gz)
    Source code(zip)
  • v0.8.0(Apr 14, 2020)

    Breaking Changes

    • Bump crossterm to 0.14.
    • Add cross symbol to the symbols list.

    Bug Fixes

    • Use the value of title_style to style the title of Axis.
    Source code(tar.gz)
    Source code(zip)
  • v0.7.0(Nov 29, 2019)


    • Use Constraint instead of integers to specify the widths of the Table widget's columns. This will allow more responsive tables.
    Table::new(header, row)
      .widths(&[15, 15, 10])
      .render(f, chunk);


    Table::new(header, row)
      .render(f, chunk);
    • Bump crossterm to 0.13.
    • Use Github Actions for CI (Travis and Azure Pipelines integrations have been deleted).


    • Add support for horizontal and vertical margins in Layout.
    Source code(tar.gz)
    Source code(zip)
  • v0.6.1(Jun 16, 2019)


    • Avoid a division by zero when all values in a barchart are equal to 0.
    • Fix the inverted cursor position in the curses backend.
    • Ensure that the correct terminal size is returned when using the crossterm backend.
    • Avoid highlighting the separator after the selected item in the Tabs widget.
    Source code(tar.gz)
    Source code(zip)
  • v0.6.0(May 18, 2019)

  • v0.5.0(Mar 10, 2019)


    • Add a new curses backend (with Windows support thanks to pancurses).
    • Add Backend::get_cursor and Backend::set_cursor methods to query and set the position of the cursor.
    • Add more constructors to the Crossterm backend.
    • Add a demo for all backends using a shared UI and application state.
    • Add Ratio as a new variant of layout Constraint. It can be used to define exact ratios constraints.


    • Add support for multiple modifiers on the same Style by changing Modifier from an enum to a bitflags struct.

    So instead of writing:

    let style = Style::default().modifier(Modifier::Italic);

    one should use:

    let style = Style::default().modifier(Modifier::ITALIC);
    // or
    let style = Style::default().modifier(Modifier::ITALIC | Modifier::BOLD);


    • Ensure correct behavoir of the alternate screens with the Crossterm backend.
    • Fix out of bounds panic when two Buffer are merged.
    Source code(tar.gz)
    Source code(zip)
  • v0.4.0(Feb 3, 2019)


    • Add a new canvas shape: Rectangle.
    • Official support of Crossterm backend.
    • Make it possible to choose the divider between Tabs.
    • Add word wrapping on Paragraph.
    • The gauge widget accepts a ratio (f64 between 0 and 1) in addition of a percentage.


    • Upgrade to Rust 2018 edition.


    • Fix rendering of double-width characters.
    • Fix race condition on the size of the terminal and expose a size that is safe to use when drawing through Frame::size.
    • Prevent unsigned int overflow on large screens.
    Source code(tar.gz)
    Source code(zip)
  • v0.3.0-beta.3(Sep 24, 2018)

  • v0.3.0-beta.2(Sep 23, 2018)


    • Remove custom termion backends. This is motivated by the fact that termion structs are meant to be combined/wrapped to provide additional functionalities to the terminal (e.g AlternateScreen, Mouse support, ...). Thus providing exclusive types do not make a lot of sense and give a false hint that additional features cannot be used together. The recommended approach is now to create your own version of stdout:
    let stdout = io::stdout().into_raw_mode()?;
    let stdout = MouseTerminal::from(stdout);
    let stdout = AlternateScreen::from(stdout);

    and then to create the corresponding termion backend:

    let backend = TermionBackend::new(stdout);

    The resulting code is more verbose but it works with all combinations of additional termion features.

    Source code(tar.gz)
    Source code(zip)
  • v0.3.0-beta.1(Sep 8, 2018)


    • Replace Item by a generic and flexible Text that can be used in both Paragraph and List widgets.
    • Remove unecessary borrows on Style.
    Source code(tar.gz)
    Source code(zip)
  • v0.3.0-beta.0(Sep 4, 2018)


    • Add a basic Crossterm backend


    • Remove Group and introduce Layout in its place
      • Terminal is no longer required to compute a layout
      • Size has been renamed Constraint
    • Widgets are rendered on a Frame instead of a Terminal in order to avoid mixing draw and render calls
    • draw on Terminal expects a closure where the UI is built by rendering widgets on the given Frame
    • Update Widget trait
      • draw takes area by value
      • render takes a Frame instead of a Terminal
    • All widgets use the consumable builder pattern
    • SelectableList can have no selected item and the highlight symbol is hidden in this case
    • Remove markup langage inside Paragraph. Paragraph now expects an iterator of Text items
    Source code(tar.gz)
    Source code(zip)
  • v0.2.3(Jun 9, 2018)

  • v0.2.2(May 6, 2018)

  • v0.2.1(Apr 1, 2018)


    • Add AlternateScreenBackend in termion backend
    • Add TermionBackend::with_stdout in order to let an user of the library provides its own termion struct
    • Add tests and documentation for Buffer::pos_of
    • Remove leading whitespaces when wrapping text


    • Fix debug_assert in Buffer::pos_of
    • Pass the style of SelectableList to the underlying List
    • Fix missing character when wrapping text
    • Fix panic when specifying layout constraints
    Source code(tar.gz)
    Source code(zip)
  • v0.2.0(Dec 26, 2017)


    • Add MouseBackend in termion backend to handle scroll and mouse events
    • Add generic Item for items in a List


    • Rename TermionBackend to RawBackend (to distinguish it from the MouseBackend)

    • Generic parameters for List to allow passing iterators as items

    • Generic parameters for Table to allow using iterators as rows and header

    • Generic parameters for Tabs

    • Rename border bitflags to Borders

    • Run latest rustfmt on all sources


    • Drop log4rs as a dev-dependencies in favor of stderrlog
    Source code(tar.gz)
    Source code(zip)
Florian Dehau
Florian Dehau
Backend service to build customer facing dashboards 10x faster. Written in Rust.

Frolic is an open source backend service (written in Rust) to build customer facing dashboards 10x faster. You can directly connect your database to t

Frolic 82 Aug 7, 2023
A Rust library for drawing grid-based user interfaces using ASCII characters.

grux A library for drawing grid-based user interfaces using ASCII characters. // Provides a uniform interface for drawing to a 2D grid. use grux::Grid

Matan Lurey 4 Jan 10, 2023
A library for building declarative text-based user interfaces

Intuitive Documentation Intuitive is a component-based library for creating text-based user interfaces (TUIs) easily. It is heavily inspired b

Enrico Borba 205 Dec 29, 2022
Tool to create web interfaces to command-line tools

webgate This command line utility allows you to: serve files and directories listed in a config file remotely run shell commands listed in a config fi

Nathan Royer 2 Jan 8, 2022
Batteries included command line interfaces.

CLI Batteries Opinionated batteries-included command line interface runtime utilities. To use it, add it to your Cargo.toml [dependencies] cli-batteri

Remco Bloemen 15 Jan 4, 2023
Demo Rust Cursive crate for terminal user interface (TUI)

Demo Rust Cursive Demonstration of the Rust programming language and Cursvie crate for terminal user interface (TUI). Setup Create: cargo new demo Add

Joel Parker Henderson 5 Dec 27, 2022
Github user information on terminal :D

octofetch Use this if youre too lazy to open github lol Installation Local install with cargo Run cargo install --git

Natapat Samutpong 65 Nov 2, 2022
a terminal user interface for lemmy

Lemmy-Terminal-Viewer Terminal User Interface for lemmy for Linux Terminals (should work in MacOs but i can't test) Install and Usage Linux Download l

Luna 21 Oct 29, 2022
OSINT from your favorite services in a friendly terminal user interface

osintui Open Source Intelligence Terminal User Interface Report Bug · Request Feature Installation First, install Rust (using the recommended rustup i

Will Sheldon 639 Jan 4, 2023
:large_orange_diamond: Build beautiful terminal tables with automatic content wrapping

Comfy-table Comfy-table tries to provide utility for building beautiful tables, while being easy to use. Features: Dynamic arrangement of content to a

Arne Beer 525 Jan 8, 2023 AI terminal assistant that can read and write your terminal directly! AI terminal assistant that read from & write to your terminal is an AI terminal assistant based on OpenAI APIs such as GPT-3.5/4! What'

hmirin 5 Jun 20, 2023
A terminal ASCII media player. View images, gifs, videos, webcam, YouTube, etc.. directly in the terminal as ASCII art.

Terminal Media Player View images, videos (files or YouTube links), webcam, etc directly in the terminal as ASCII. All images you see below are just m

Max Curzi 36 May 8, 2023
The auto-managed -sys crate for Apple platforms using bindgen directly from build environment

apple-sys Apple platforms have a rather monotonous programming environment compared to other platforms. On several development machines, we will depen

Jeong, YunWon 34 Apr 17, 2023
A rust crate for rendering large text to the terminal using font8x8 and ratatui.

tui-big-text tui-big-text is a rust crate that renders large pixel text as a ratatui widget using the glyphs from the font8x8 crate. Installation carg

Josh McKinney 7 Sep 7, 2023
Terminal UI to chat with large language models (LLM) using different model backends, and integrations with your favourite editors!

Oatmeal Terminal UI to chat with large language models (LLM) using different model backends, and integrations with your favourite editors! Overview In

Dustin Blackman 88 Dec 4, 2023
Estratto is a powerful and user-friendly Rust library designed for extracting rich audio features from digital audio signals.

estratto 〜 An Audio Feature Extraction Library estratto is a powerful and user-friendly Rust library designed for extracting rich audio features from

Amber J Blue 5 Aug 25, 2023
fd is a program to find entries in your filesystem. It is a simple, fast and user-friendly alternative to find

fd is a program to find entries in your filesystem. It is a simple, fast and user-friendly alternative to find. While it does not aim to support all of find's powerful functionality, it provides sensible (opinionated) defaults for a majority of use cases.

David Peter 25.9k Jan 9, 2023
A user-friendly TUI for secure file transfers, with arrow-key and VIM-style navigation

gsftp SFTP with an interactive text-based user interface (TUI). Transfer files through an encrypted connection with a visual interface, so you can see

Ben Jiron 3 Jul 7, 2022
Repository containing schedules, slides/talk and user submissions for the 2023 devconnect hackerhouse

2023 DevConnect Hacker House Content Schedule Hackathon Tracks xChain dapps - Total Prize pool of USD 12k (5k, 4k, 3k) Fully on-chain dapps - Total Pr

Internet Computer Hackers Den 8 Nov 22, 2023