console: the small terminal abstraction Rust CLI libraries build on
A rust console and terminal abstraction
At a glance
- What is it?
- console is a Rust crate that gives a program access to terminal features such as colours, styles, cursor movement and text width measurement, and it is the layer underneath crates like dialoguer and indicatif. The judgement is that its value is in the details nobody wants to reimplement, and that its Windows behaviour is the place to look before you depend on it.
- Who is it for?
- console fits a Rust author writing a command line tool that needs colours that respect the tty and the clicolors standard, text width measured the way the terminal will actually render it, and cursor control that works on both Unix and Windows without a platform dependency of your own.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Term gives you a terminal either live or buffered
The `console::Term` type is the entry point to everything the crate does, and it has one design decision that shapes how you use it.
A `Term` can either give you direct access to the connected terminal or buffer up the commands you issue. The buffered mode is what you want for a program that must not interleave its own output with someone else's, and for a program that wants to draw something and then erase it. The documented caveat is specific: a buffered terminal is not completely buffered on Windows, where cursor movements are currently passed straight through.
The usage is a two-liner that shows the intended shape:
use std::thread;
use std::time::Duration;
use console::Term;
let term = Term::stdout();
term.write_line("Hello World!")?;
thread::sleep(Duration::from_millis(2000));
term.clear_line()?;That is a progress line: print something, wait, clear it. The `?` propagation matters here, since terminal writes fail when the output is a closed pipe, and a CLI that panics with a broken pipe error on `Ctrl-C` in a pipeline is a common and avoidable annoyance.
The repository has five runnable examples, which together map the feature surface: `colors.rs`, `colors256.rs`, `cursor_at.rs`, `keyboard.rs` and `term.rs`. Each is declared in `Cargo.toml` with `required-features = ["std"]`, so they are skipped in a no-std build rather than failing to compile.
The crate is a library, not a binary. It is published on crates.io as `console`, and the documentation lives on docs.rs. The homepage is the GitHub repository, and the version in `Cargo.toml` is `0.16.6`.
Colour decisions follow the clicolors standard, not a guess
The crate decides whether to emit colour based on the tty flag, and it does so following the clicolors standard, which is the de facto convention for how a program should react to `NO_COLOR`, `CLICOLOR` and `CLICOLOR_FORCE` in the environment.
That decision matters more than it sounds. A program that hardcodes escape sequences produces unreadable output when piped into a file or a pager, and every CLI author has rediscovered this problem. Following a published standard means the behaviour matches what the user's terminal and what other tools already expect.
On top of that, the crate offers higher level wrappers. There is a `style` function for inline styling, and there are `Style` values you can hold on to and apply later:
use console::style;
println!("This is {} neat", style("quite").cyan());use console::Style;
let cyan = Style::new().cyan();
println!("This is {} neat", cyan.apply_to("quite"));The distinction between the two is a small design point with a real payoff. The `style` function produces something that implements `Display`, so it composes into format strings. The `Style` type is a value you can build once and reuse, and `apply_to` renders it against a particular piece of text. For a program that colours hundreds of lines by category, keeping a set of `Style` values avoids re-deriving the escape sequences per line.
On Windows there is a separate mechanism, the `windows-console-colors` feature, which enables the legacy console colour API and depends on `ansi-parsing`. The `Cargo.toml` lists `windows-sys` with the `Win32_System_Console`, `Win32_Foundation`, `Win32_Storage_FileSystem` and `Win32_UI_Input_KeyboardAndMouse` features for Windows targets, plus `encode_unicode` for console output encoding. The Windows support is therefore not a thin ANSI passthrough, it is a real platform layer.
Width measurement and ANSI stripping are the two functions you will reuse
The crate provides `strip_ansi_codes` to remove ANSI escape sequences from a string, and `measure_text_width` to calculate how wide a string will be as the terminal displays it.
These two are a matched pair, and the documentation is explicit that together they are useful for more complex formatting. Strip the codes from a string and you can measure the visible width; measure the visible width and you know how many columns to pad, to truncate, or to leave before a cursor movement.
That is the part of terminal handling that most CLI tools get wrong, and getting it wrong is what produces a progress bar that jitters or a table whose columns drift when a value contains a colour code. The naive approach of using `str::len()` counts bytes, which is wrong for anything non-ASCII, and counting characters with `chars().count()` is wrong for wide characters and for combining marks.
The crate's answer is to depend on the `unicode-width` crate by default, which is what computes terminal character widths properly. The feature is optional and, the documentation notes, disabling it cuts down on dependencies. That is a deliberate trade you can make in a size-constrained build, at the cost of correct measurement for wide characters.
For anyone writing a progress bar, a table renderer or a spinner, these two functions are probably the entire reason to depend on this crate rather than writing escape sequences by hand.
The feature matrix is what makes the crate portable
The `Cargo.toml` feature list is short and each entry is a real portability decision.
The default set is `unicode-width`, `ansi-parsing` and `std`. The `std` feature pulls in `alloc` and the optional `libc` dependency, and `alloc` can be used on its own. `ansi-parsing` gates the escape-sequence parsing that the width and stripping functions need, and `windows-console-colors` depends on it and turns on the legacy Windows console colour path.
This feature design lets the crate work in environments where the standard library is not available, which is why the test matrix in the `Makefile` is worth reading. The `test` target runs the suite seven different ways: plain `cargo test`, `cargo test --all-features`, then `--lib --no-default-features`, `--lib --no-default-features --features alloc`, `--no-default-features --features std`, `--no-default-features --features std,ansi-parsing`, and `--no-default-features --features std,unicode-width`.
A crate that claims `no_std` support and then quietly breaks when you enable one feature is common. Running the suite across every feature combination is the check, and the Makefile does it.
There is also a `check-minver` target that runs `cargo minimal-versions check --lib` in three configurations, which verifies that the declared lower bounds on dependencies actually work. The `rust-version` field says `1.71`, so that is the floor for the toolchain.
The dependency list itself is minimal: `libc` at `0.2.99`, optional, and `unicode-width` at `0.2`, optional. For a terminal library, that is a good outcome, since a CLI tool that pulls in a tree of transitive dependencies is a CLI tool people hesitate to depend on.
The Makefile is the project's own quality gate
The `Makefile` at the repository root is the clearest statement of what this project considers done.
`all` is the default target and it runs `test`, which runs the feature matrix described above. `make check` is `cargo check --lib --all-features`, `make build` is `cargo build --all-features`, and `make doc` is `cargo doc --all-features`. The lint target installs the clippy component if it is missing and then runs `cargo clippy --examples --tests --all-features -- --deny warnings`, so warnings are errors.
Formatting has both a write and a check form, `make format` and `make format-check`, and both install the `rustfmt` component on demand. The check-minver target is the one that stands out, since minimal-versions resolution is a stricter test than the default resolver's.
Two details in `Cargo.toml` show the maintainers' attention to edge cases. The dev dependency on `proptest` is configured with `default-features = false` and a specific feature list, with a comment explaining that this picks a setup which works with wasi. And the benchmark section is present but commented out, with a note that the criterion dependency and bench targets are currently disabled and can be uncommented if you want to play around with the benchmarks. The `benches/` directory exists in the repository regardless, and the `tests/data/sample_zellij_session.log` file is explicitly included in the published package, which is a hint that a test fixture depends on a real terminal session capture.
Continuous integration is wired through the `ci.yml` workflow, referenced by the build status badge in the README.
Where console stops and dialoguer and indicatif begin
The README positions console as best paired with two other crates, and the division of labour is clean enough to be worth stating precisely.
`console` is the primitive layer. It gives you a terminal, colours, styles, cursor control, width measurement and ANSI stripping. It does not give you a prompt, a progress bar, or a spinner. Those are what `dialoguer` and `indicatif` are for, and both build on the same foundation.
So the alternative to depending on console is not another terminal library in the same category. It is writing the escape sequences yourself, which means reimplementing colour detection against the tty flag, reimplementing width measurement with the same edge cases, and reimplementing Windows console handling with a platform dependency of your own. The cost of that is not the code, it is the maintenance of the edge cases across platforms you do not use.
The trade is a dependency, and console is a small one: two optional dependencies, one of which is `libc`. The MIT licence allows embedding it in a closed-source CLI as long as the copyright notice is kept, which is a licensing observation rather than legal advice, and the `LICENSE` file in the repository is the text that governs.
For maintenance, the release history is steady rather than fast: 0.16.4 on 2026-07-01, 0.16.5 on 2026-08-13 and 0.16.6 on 2026-09-10, with the last push to the repository on 2026-09-10. A 0.x version line means the semver guarantees are not in force, so a minor bump can carry breaking changes, and the `CHANGELOG.md` in the repository root is the place to check before upgrading.
Editorial conclusion
console fits a Rust author writing a command line tool that needs colours that respect the tty and the clicolors standard, text width measured the way the terminal will actually render it, and cursor control that works on both Unix and Windows without a platform dependency of your own. It does not fit a project that needs a full-screen terminal emulator, a prompt widget library or a progress bar, since those are the jobs of dialoguer and indicatif, and it does not fit a `no_std` target left on the default features, because the default feature set pulls in `std`. Verify first that the `windows-console-colors` feature is enabled on Windows if you rely on the legacy console colour API, that your minimum supported Rust version of 1.71 matches your toolchain, and that the `unicode-width` feature is on if your output contains CJK or emoji, since disabling it changes how width is measured.
Frequently asked questions
How do I add coloured output in a Rust CLI with the console crate?
Use the `style` function for inline styling, as in `println!("This is {} neat", style("quite").cyan())`, or build a `Style` value and apply it later with `apply_to`. The crate detects whether to emit colour from the tty flag, following the clicolors standard.
How does the console crate handle Windows terminals?
Windows support is not a plain ANSI passthrough. The `windows-console-colors` feature enables the legacy console colour API and depends on `ansi-parsing`, and the crate depends on `windows-sys` with the console, input and filesystem features for Windows targets. One documented caveat is that a buffered `Term` is not completely buffered on Windows, where cursor movements pass straight through.
What is the difference between strip_ansi_codes and measure_text_width in console?
`strip_ansi_codes` removes ANSI escape sequences from a string and `measure_text_width` calculates the width a string occupies as the terminal displays it. The documentation notes that the two together are useful for more complex formatting, and width measurement relies on the optional `unicode-width` crate by default.
Can I use the console crate without the standard library?
The crate is designed for it. The `std` feature is optional and pulls in `alloc` and `libc`, and the Makefile test target runs the suite with `--no-default-features` and with individual features such as `alloc`, `std`, `ansi-parsing` and `unicode-width` to verify each combination.
What examples does the console crate repository ship with?
The repository keeps runnable examples in `examples/`, and each is declared in `Cargo.toml` under `[[example]]` with a `name` and `required-features` list, for example `required-features = ["std"]`. The existing examples cover colours, 256 colours, cursor position, keyboard input and the `Term` type.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/console-rs-console)Was this useful?