A tiny Streamlit-inspired terminal UI experiment.
Project description
stui
stui is a small Streamlit-inspired framework for building terminal-native
Python apps. Write a short script, run it in your terminal, and get a Textual UI
with stateful controls, reruns, and a compact public API.
It is built for local tools, demos, data scripts, model debug panels, SSH sessions, and headless environments where opening a browser, binding a port, or running a dashboard server is unnecessary ceremony. The public API is deliberately small and readable.
stui is serious about being smaller than the thing that inspired it. It is
not official Streamlit, is not affiliated with Streamlit, and is not a
Streamlit compatibility layer. Existing Streamlit apps usually need edits; new
stui apps should be written against the documented terminal-first API below.
Preview
┌─ stui ───────────────────────────────────────────┐
│ stui demo basic │
│ │
│ x │
│ [██░░░░░░░░░░░░] 10 │
│ │
│ [ Increment ] │
│ │
│ x = 10 │
│ count = 0 │
│ │
│ q Quit r Rerun tab Focus next │
└──────────────────────────────────────────────────┘
Install
Use Python 3.11 or newer.
Install the PyPI distribution named stui-terminal. The import package and
command are both still named stui, which is the name used throughout the
examples and API reference:
python -m pip install stui-terminal
import stui as st
Check the installed version, diagnostics, and terminal environment before filing terminal or keyboard issues:
python -m stui --version
python -m stui doctor
python -m stui doctor --json
python -m stui doctor --compat
python -m stui selftest
For local development from a checkout, use an editable install with the dev extra:
python3.11 -m venv .venv
. .venv/bin/activate
python -m pip install -e ".[dev]"
For runtime-only local use, install without the dev extra:
python -m pip install -e .
The distribution/import split is intentional:
- PyPI package:
stui-terminal - Python import:
import stui as st - CLI command:
stui - Module CLI fallback:
python -m stui
That split is also what users see on PyPI: install stui-terminal, then import
and run stui.
60-Second Quickstart
Create a file named app.py:
import stui as st
st.title("Hello from the terminal")
name = st.text_input("Name", "MarMar")
level = st.slider("Level", 1, 10, 5)
if st.button("Greet"):
st.success(f"Hi {name}. Level {level} selected.")
Install, launch a bundled demo, then run the app you just wrote:
python -m pip install stui-terminal
stui demo model_demo
stui demo dashboard
stui run app.py
Press q to quit a demo. The screenshot above is a real terminal capture from
the bundled model_demo demo.
Or generate a starter file instead of writing app.py by hand:
stui init starter_app.py
stui run starter_app.py
If the stui command is not on your PATH, use the module entry point:
python -m stui run app.py
Check your install and terminal details:
stui --version
stui doctor
stui doctor --json
stui doctor --compat
stui selftest
stui selftest --json
stui selftest --strict
stui selftest --strict --repeat 2
stui check app.py
stui check app.py --json
stui check app.py --strict
stui check app.py --strict --repeat 2
Project Links
- Docs index
- API reference
- API stability
- Terminal compatibility
- Changelog
- Roadmap
- Release notes
- Contributing
- Support
- Security
Build Your First App
stui scripts rerun when users interact with widgets. Use
st.session_state for values that should survive reruns:
import stui as st
st.title("Counter")
if "count" not in st.session_state:
st.session_state.count = 0
step = st.slider("Step", 1, 10, 1)
if st.button("Add"):
st.session_state.count += step
if st.button("Reset"):
st.session_state.count = 0
st.write("count =", st.session_state.count)
Run it with:
stui run app.py
Start from the repository examples when you want a larger reference. These paths exist only after you clone the repository:
stui run examples/counter.py
stui run examples/inputs.py
stui run examples/data_display.py
stui run examples/dashboard.py
stui run examples/forms.py
stui run examples/layouts.py
stui run examples/charts.py
stui run examples/kitchen_sink.py
Those examples/... paths are repository files. Installed packages also expose
bundled examples that can be listed or copied into any working directory:
stui demo list
stui demo basic
stui demo model_demo
stui demo dashboard
stui demo forms
stui demo charts
stui demo kitchen_sink
stui examples
stui example list
stui example copy basic ./basic.py
stui run ./basic.py
stui example copy counter ./counter.py
stui run ./counter.py
stui init ./new_app.py
stui init ./dashboard.py --template dashboard
stui init ./data_app.py --template data
stui init ./charts_app.py --template charts
stui init ./forms_app.py --template forms
stui selftest
stui selftest --strict
Automated tests cover demo CLI behavior and bundled-resource resolution without
starting a full TUI. For an interactive smoke check, run stui demo dashboard
from any directory and press q to quit.
stui init currently supports the basic, dashboard, data, charts, and
forms templates. Use python -m stui ... for the same commands when the
stui script directory is not on PATH.
If stui doctor runs from CI or another non-interactive shell, it may report
TERM=dumb, no TTY, or a 0x0 terminal size. For terminal rendering
diagnostics, run it in the real terminal where you plan to use stui.
Copied examples and generated starter files are plain Python scripts. They do not require a checkout after they are copied:
stui example copy forms ./forms_app.py
python -m stui run ./forms_app.py
stui init ./signup.py --template forms
python -m stui run ./signup.py
stui init ./ops_dashboard.py --template dashboard
python -m stui run ./ops_dashboard.py
stui init ./data_app.py --template data
python -m stui check ./data_app.py --strict
stui init ./charts_app.py --template charts
python -m stui check ./charts_app.py --strict
The v1.x releases treat these demo/example/init/copy/check/selftest commands as part of the stable documentation contract. If an installed-package flow does not work without a repository checkout, that is patch-release-worthy docs or packaging debt.
The demo screenshot above is generated from a real terminal app in this repository, not a browser mockup. If the image on PyPI or GitHub ever drifts from the current package behavior, treat that as release polish debt.
For more detail, see the API reference, API stability labels, terminal compatibility matrix, and v1 readiness checklist.
Copy-Paste Examples
Slider and Button
import stui as st
st.title("Slider and Button")
if "runs" not in st.session_state:
st.session_state.runs = 0
threshold = st.slider("Threshold", 0.0, 1.0, 0.5, step=0.1)
if st.button("Run"):
st.session_state.runs += 1
st.success(f"Run {st.session_state.runs} at threshold {threshold}")
Inputs
import stui as st
st.title("Inputs")
name = st.text_input("Name", "MarMar")
batch = st.number_input("Batch size", min_value=1, max_value=128, value=16)
model = st.selectbox("Model", ["tiny", "base", "large"], index=1)
mode = st.radio("Mode", ["fast", "balanced", "careful"], index=1)
dry_run = st.checkbox("Dry run", value=True)
st.write("name =", name)
st.write("batch =", batch)
st.write("model =", model)
st.write("mode =", mode)
st.write("dry run =", dry_run)
Table and Dataframe
import stui as st
st.title("Runs")
rows = [
{"name": "baseline", "accuracy": 0.81, "latency_ms": 42},
{"name": "quantized", "accuracy": 0.79, "latency_ms": 24},
{"name": "distilled", "accuracy": 0.77, "latency_ms": 18},
]
st.table(rows)
st.dataframe({"setting": ["device", "batch"], "value": ["cpu", 16]})
Progress and Status
import stui as st
st.title("Job Status")
complete = st.slider("Complete", 0, 100, 35)
st.progress(complete, text="current job")
if complete == 100:
st.success("Done")
elif complete >= 75:
st.warning("Almost there")
else:
st.info("Running")
Why terminal-native?
Some useful Python apps do not need a browser runtime. stui keeps the
interface inside the terminal so it can fit naturally into:
- SSH sessions, remote machines, and headless boxes.
- Internal tools where opening ports or managing local server URLs is friction.
- Offline or locked-down environments where browser access is limited.
- Model, data, and DevOps workflows that already start from a shell.
That also keeps the boundary simple: stui does not start a web server, use
websockets, require port-forwarding, or depend on Streamlit at runtime.
Commands
# Install the package from PyPI.
python -m pip install stui-terminal
# Run a bundled first-run demo, then create and run an app.
stui demo dashboard
stui init app.py
stui check app.py
stui run app.py
python -m stui run app.py
# List, copy, or create starter examples.
stui demo list
stui examples
stui example list
stui example copy counter ./counter.py
stui init ./new_app.py
stui init ./dashboard.py --template dashboard
stui init ./forms_app.py --template forms
# Print version and install/terminal diagnostics.
stui --version
stui doctor
stui doctor --json
stui check app.py --json
stui check app.py --strict --repeat 2
stui selftest --strict --repeat 2
# Install the project for local development from a checkout.
python3.11 -m venv .venv
. .venv/bin/activate
python -m pip install -e ".[dev]"
# Run the smoke-size example app.
stui run examples/basic.py
# Run the stateful counter example.
stui run examples/counter.py
# Run the deterministic model-parameter demo.
stui run examples/model_demo.py
# Run the test suite.
python3.11 -m pytest
Keyboard Shortcuts
q: quit the appr: rerun the scripttab: focus the next widgetshift+tab: focus the previous widgetenterorspace: press the focused buttonspace: toggle the focused checkboxenterin text and number inputs: submit the edited valueenter,right, ordown: move a selectbox to the next choiceleftorup: move a selectbox to the previous choice- arrow keys in radio groups: choose another option
enterorspace: toggle a focused expanderleftorh: decrease the focused sliderrightorl: increase the focused sliderhome: set the focused slider to its minimum valueend: set the focused slider to its maximum value
Some lower-level editing and focus behavior comes from Textual and can vary by terminal. See Terminal Compatibility and the v1 compatibility gate for the current checklist.
API
Import the API as:
import stui as st
The public API is intentionally compact and Streamlit-inspired, not Streamlit-compatible.
For the working API reference, see docs/api-reference.md. The current API contract and v1 stability checklist are tracked in docs/v1-readiness.md#api-contract-status and docs/v1-readiness.md#stable-api. The terminal support checklist lives in docs/terminal-compatibility.md.
| Area | APIs | Status in v1.8.0 |
|---|---|---|
| Text | st.title, st.header, st.subheader, st.caption, st.text, st.markdown, st.write, st.divider |
v1-stable |
| Status | st.info, st.success, st.warning, st.error, st.exception |
v1-stable |
| Status/help primitives | st.status, st.spinner, st.help |
Post-v1 experimental while terminal grouping/help formatting gathers feedback |
| Display | st.code, st.json, st.progress, st.table, st.dataframe |
v1-stable static terminal displays |
| Inputs | st.button, st.slider, st.text_input, st.checkbox, st.number_input, st.selectbox, st.radio |
v1-stable input widgets |
| Forms | st.form, st.form_submit_button |
v1-stable deferred commit to session_state until submit |
| Layout/grouping | st.container, st.expander, st.columns |
v1-stable terminal grouping primitives; st.columns is count-only and stacks on narrow terminals |
| Metrics and charts | st.metric, st.bar_chart, st.line_chart |
v1-stable compact terminal summaries, not plotting replacements |
| State and flow | st.session_state, st.rerun, st.stop |
v1-stable state and flow-control helpers |
| Package metadata | st.__version__ |
v1-stable package version string |
| CLI and examples | stui run, stui check, stui selftest, stui demo list, stui demo NAME, stui examples, stui example list, stui example copy, stui init, stui doctor, stui --version |
v1-stable command surface |
Inputs support stable key values and optional callbacks where the function
signature documents them. Tables and charts are simple static displays and do
not require pandas or plotting dependencies.
Stable API
The v1.8.0 stable surface keeps the v1 core compact while improving repeated validation, release evidence, state rollback, data display, and count-only layout behavior. Tables and dataframes are stable for documented scalar, list, mapping, dataframe-like, dataclass, namedtuple, and simple public-object shapes; charts are stable for the documented scalar, list, mapping, tuple-pair, list-of-dicts, and dict-of-columns shapes. These names should keep their call shape, return type, and basic behavior through v1 unless a correctness, terminal, or security issue forces a change.
Experimental API
The documented experimental APIs are public enough to try, but they are not
promised as frozen v1 behavior yet. In v1.8.0 this includes st.status,
st.spinner, and st.help.
Release notes should call out any change with a migration note when practical.
APIs not shown in this table should be treated as private implementation
details. Experimental display/status helpers may still tighten in v1.x unless they
are promoted in the API stability docs and covered by release notes.
Deferred APIs for v1 include st.sidebar, st.tabs, st.file_uploader,
st.cache_data, st.cache_resource, st.components, editable dataframes,
custom column ratios/gaps, st.empty, plotting-library parity, and
browser/server runtime features.
Compatibility
stui is Streamlit-inspired, not Streamlit-compatible. Existing Streamlit apps
usually need small edits before they run in stui; unsupported calls should be
removed or replaced with the compact API above.
Runtime expectations:
- Python 3.11 or newer.
- Terminal UI powered by Textual and Rich.
- No Streamlit runtime dependency.
- No browser tab, local web server, websocket, or port-forwarding flow.
- Static table/dataframe display without dataframe editing or sorting.
- Common modern terminals should work best with UTF-8, color support, and a
normal interactive
TERMsuch asxterm-256color.
See Terminal Compatibility for the current evidence matrix and report format. v1.x stays evidence-driven: common modern terminals are expected targets, but environments without project-owned evidence remain labeled test-needed instead of claimed as fully supported.
Common Mistakes
- Installing
stuiinstead ofstui-terminal. The PyPI package isstui-terminal; the import and CLI arestui. - Running
stui runfrom a different Python environment than the one where the package was installed. Trypython -m stui run app.py. - Expecting a browser dashboard.
stuirenders inside your terminal. - Reusing Streamlit-only APIs such as sidebars, file upload, caching decorators, or arbitrary components. They are not part of this small API.
- Assuming newer APIs are present in an older install. Check
python -c "import stui; print(stui.__version__)"before using forms, grouping primitives, metrics, charts, or packaged examples. - Doing slow network or model work at top level. Scripts rerun after interactions, so keep top-level work light and cache expensive work yourself.
- Forgetting stable
keyvalues when creating similar widgets in loops.
Troubleshooting
stui: command not found
Make sure you installed into the same Python environment that your shell is using:
python -m pip install stui-terminal
python -m stui --version
If python -m stui --version works but stui --version does not, your
environment's script directory is not on PATH. Running through
python -m stui ... is a reliable workaround.
Python Version
stui requires Python 3.11 or newer:
python --version
If that prints an older version, create a 3.11+ environment first:
python3.11 -m venv .venv
. .venv/bin/activate
python -m pip install stui-terminal
Terminal Rendering
stui renders a Textual app inside your terminal. For the best results, use a
modern terminal with UTF-8 and color support. If borders, focus rings, or block
characters look wrong, try another terminal app, make the window wider, and
check that TERM is set to a normal interactive terminal value such as
xterm-256color.
If a report involves color or theme behavior, include:
stui doctor --compat
stui doctor --json
NO_COLOR is reported for diagnostics, but final color behavior still depends
on Rich, Textual, and the terminal. Unsupported STUI_THEME values fall back to
the default theme and are reported by stui doctor.
Selftest Version Mismatch
If stui --version and stui selftest disagree about the installed package
version, the active environment probably has stale editable-install metadata.
Reinstall in the active environment before reporting it:
python -m pip install -e ".[dev]"
stui selftest --json
macOS Editable Install Quirk
If a local editable install appears to succeed but import stui or
stui --version cannot find the package on macOS, check whether the virtual
environment or editable-install .pth file was marked hidden:
chflags -R nohidden .venv
python -m pip install -e ".[dev]"
python -m stui --version
Examples
The commands below use repository paths from a checkout. If you installed
stui-terminal from PyPI and do not have this repository, copy a bundled
example first:
stui example copy counter ./counter.py
stui run ./counter.py
Counter
examples/counter.py shows a minimal stateful app with increment, decrement, and
reset controls.
stui run examples/counter.py
Model Demo
examples/model_demo.py shows a small model-parameter playground using text
input, checkbox, sliders, status messages, session state, and deterministic
scoring. It is intentionally local and fake: there are no network calls or model
dependencies.
stui run examples/model_demo.py
Inputs
examples/inputs.py shows text, numeric, selectbox, radio, checkbox, and button
controls together.
stui run examples/inputs.py
Data Display
examples/data_display.py shows static tables, dataframe-style display, JSON,
and code output. It includes list-of-dicts rows, dict-of-columns data,
max_rows/max_cols, dataclass-like object rows, and multiline cell
normalization.
stui run examples/data_display.py
From an installed package without a repository checkout:
stui example copy data_display ./data_display.py
stui run ./data_display.py
Dashboard
examples/dashboard.py combines controls, progress, status messages, and a
small table into a compact terminal control panel.
stui run examples/dashboard.py
Forms
examples/forms.py shows the stable form flow: form widget display values can
change during reruns, but keyed values commit to session_state on submit.
stui run examples/forms.py
Layouts
examples/layouts.py shows responsive columns, container, data inside grouped
sections, and keyboard-toggleable expander patterns. Columns stack in narrow
terminals, including nested columns inside a parent column. The design notes are in
docs/layouts.md.
stui run examples/layouts.py
From an installed package without a repository checkout:
stui example copy layouts ./layouts.py
stui run ./layouts.py
Charts
examples/charts.py shows stable metric, bar_chart, and line_chart
helpers with list, mapping, tuple-pair, and column-shaped data.
stui run examples/charts.py
Kitchen Sink
examples/kitchen_sink.py exercises the stable API surface plus the
experimental status/help terminal primitives that remain useful feedback
targets before v1.
stui run examples/kitchen_sink.py
Limitations
- No browser, web server, websocket, or port-forwarding runtime.
- No Streamlit dependency and no promise of Streamlit compatibility.
- Forms still rerun the Textual app when a form widget changes, but pending
form values stay out of
session_stateuntil submit. - Expanders are keyboard-toggleable with Enter/Space and persist their state; this is still a modest terminal grouping primitive, not a full layout system.
- Columns accept only an integer count, stack when the terminal is narrow, and do not support custom ratios, sidebars, tabs, browser grids, or horizontal scrolling. Nested columns stack based on the parent column width, but deep nesting can still become hard to read in small terminals.
- Charts are compact terminal summaries, not plotting-library replacements.
st.bar_chartandst.line_chartsupport documented numeric terminal data shapes, including simple sequences, mappings, tuple pairs, list-of-dicts, and dict-of-columns inputs. st.status,st.spinner, andst.helpare display helpers, not live animation, background task, or pager systems.st.rerunandst.stopare small flow-control helpers, not a full job scheduler or async runtime.- No sidebars, file upload, browser components, or caching decorators yet.
- Tables are static display only; there is no full dataframe editing or sorting. Object-row support is for display only and uses dataclasses, namedtuples, or simple public attributes.
- Slider input supports numeric values only.
- Layout remains terminal-first and intentionally modest.
- The app reruns the script as interactions change state, so examples should keep top-level work lightweight.
- There is no built-in cache yet. Keep expensive network, model, file, or data loading work behind normal Python guards or user-triggered actions.
- Error handling is development-oriented, but
stui check --strictand repeated validation help catch common app-authoring problems before sharing. - Experimental APIs remain public, but may still tighten in v1.x releases with release-note coverage and migration notes when practical.
Non-Goals
stui is deliberately not trying to become:
- A Streamlit compatibility layer or migration tool.
- A browser dashboard framework.
- A hosted/cloud product with auth, sync, collaboration, or deployment management.
- A plotting library or dataframe editor.
- A large component marketplace before the terminal API is stable.
- A wrapper around GPL slider/widget code or
textual-slider.
v1.8.0 Stable Status
v1.8.0 is a performance, reliability, and long-run quality release. It keeps
the package/import/CLI contract from v1.0.0, keeps v1.4.0 through v1.7.0
behavior compatible, adds repeated non-interactive validation through
stui check --repeat and stui selftest --repeat, and hardens rollback
behavior for authoring errors, hidden form fields, and rerun storms.
The remaining experimental APIs and terminal compatibility unknowns are visible instead of hidden. Post-v1 work should be feedback-driven and kept out of the core stable API unless it has enough real terminal evidence.
See ROADMAP.md, docs/README.md, and docs/v1-readiness.md for the current v1.x contract and post-v1 direction.
Contributing
See CONTRIBUTING.md for the local development workflow and project boundaries.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file stui_terminal-1.8.0.tar.gz.
File metadata
- Download URL: stui_terminal-1.8.0.tar.gz
- Upload date:
- Size: 1.0 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ee4c1a80896cce0dc991ffb1897c8337d5ea363abc3a3ede7d2854c5043f8578
|
|
| MD5 |
dd28dd3aedb1863e4634f5e128eb9abf
|
|
| BLAKE2b-256 |
9cc5e557401bd6c45660a7324fa872cdc6516a2cd041d5aeead398296ef49c07
|
Provenance
The following attestation bundles were made for stui_terminal-1.8.0.tar.gz:
Publisher:
publish.yml on marmar9615-cloud/stui-terminal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
stui_terminal-1.8.0.tar.gz -
Subject digest:
ee4c1a80896cce0dc991ffb1897c8337d5ea363abc3a3ede7d2854c5043f8578 - Sigstore transparency entry: 1907318653
- Sigstore integration time:
-
Permalink:
marmar9615-cloud/stui-terminal@5a16121423baeb7b2bb96cab4dab2ee3fbd2904e -
Branch / Tag:
refs/tags/v1.8.0 - Owner: https://github.com/marmar9615-cloud
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5a16121423baeb7b2bb96cab4dab2ee3fbd2904e -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file stui_terminal-1.8.0-py3-none-any.whl.
File metadata
- Download URL: stui_terminal-1.8.0-py3-none-any.whl
- Upload date:
- Size: 49.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0acf0dd7ef8c04cacc9942db71194a75ae2454080798f172bedad2a9ea6dbf4a
|
|
| MD5 |
195a20a0f3986a5ff6e48efa0b1b4201
|
|
| BLAKE2b-256 |
f68a9bbb1ad1f8464cb11a8c8f29bd1b4656aff0295aa3b2c48f7d05777ef0ca
|
Provenance
The following attestation bundles were made for stui_terminal-1.8.0-py3-none-any.whl:
Publisher:
publish.yml on marmar9615-cloud/stui-terminal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
stui_terminal-1.8.0-py3-none-any.whl -
Subject digest:
0acf0dd7ef8c04cacc9942db71194a75ae2454080798f172bedad2a9ea6dbf4a - Sigstore transparency entry: 1907318785
- Sigstore integration time:
-
Permalink:
marmar9615-cloud/stui-terminal@5a16121423baeb7b2bb96cab4dab2ee3fbd2904e -
Branch / Tag:
refs/tags/v1.8.0 - Owner: https://github.com/marmar9615-cloud
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5a16121423baeb7b2bb96cab4dab2ee3fbd2904e -
Trigger Event:
workflow_dispatch
-
Statement type: