A Streamlit text input that updates on every keystroke, no Enter key required.
Project description
streamlit-live-search
A Streamlit text input that behaves exactly like st.text_input(), except it reports every keystroke back to Python immediately — no Enter key, no loss of focus required.
User types: b → Python receives: "b"
User types: bi → Python receives: "bi"
User types: bid → Python receives: "bid"
Built with a React + TypeScript frontend and a small Python wrapper around the Streamlit Components API.
Features
- ⚡ Real-time updates on every keystroke — no Enter key needed
- 🎚️ Configurable debounce (
0for instant, or any millisecond delay) - 🏷️ Label, placeholder, help text, and
label_visibilitysupport - 📐 Configurable width / height
- 🚫
disabledstate - ✖️ Built-in clear button (and
Escto clear) - 🎯 Autofocus support
- ⌨️ Keyboard shortcuts (
Escto clear,Ctrl/Cmd+Kto focus) - 🌗 Automatic dark mode / light mode (follows the active Streamlit theme)
- 📱 Responsive layout
- ♿ Accessible: ARIA labels, keyboard navigation, screen-reader friendly
- 🧪 Unit-tested on both the Python and frontend side
Installation
pip install streamlit-live-search
Requires Python 3.8+ and Streamlit 1.28+.
Quick start
import streamlit as st
from streamlit_live_search import live_search
search = live_search(
label="Search Student",
placeholder="Type student name...",
debounce=0,
key="search",
)
if search:
st.write(f"You typed: {search}")
Live-filtering a table
import pandas as pd
import streamlit as st
from streamlit_live_search import live_search
search = live_search(label="Search Student", placeholder="Type student name...")
df = pd.read_csv("students.csv")
if search:
df = df[df["name"].str.contains(search, case=False, na=False)]
st.dataframe(df)
Typing b instantly filters the table; typing bi filters again — with no Enter key press at any point. See examples/app.py for a complete, runnable example (including an st-aggrid table variant).
Run the example locally:
pip install streamlit-live-search pandas
streamlit run examples/app.py
API reference
live_search(...)
live_search(
label: str = "",
placeholder: str = "",
value: str = "",
debounce: int = 100,
key: str | None = None,
disabled: bool = False,
width: str | int | None = None,
height: str | int | None = None,
clearable: bool = True,
autofocus: bool = False,
label_visibility: str = "visible",
help: str | None = None,
) -> str
| Argument | Type | Default | Description |
|---|---|---|---|
label |
str |
"" |
Label shown above the field. Empty string omits it. |
placeholder |
str |
"" |
Placeholder text shown when empty. |
value |
str |
"" |
Initial value on first render. |
debounce |
int |
100 |
Milliseconds to wait after the last keystroke before updating Python. Use 0 for instant, uncoalesced updates. |
key |
str | None |
None |
Streamlit widget key. Required if rendering more than one live_search with identical arguments. |
disabled |
bool |
False |
Renders the field read-only. |
width |
str | int | None |
None |
CSS width, e.g. "300px" or "100%". |
height |
str | int | None |
None |
CSS height of the widget container. |
clearable |
bool |
True |
Shows a clear ("×") button once text is entered; binds Esc to clear. |
autofocus |
bool |
False |
Focuses the field automatically on mount. |
label_visibility |
"visible" | "hidden" | "collapsed" |
"visible" |
Matches core Streamlit widget semantics. |
help |
str | None |
None |
Helper text rendered below the field. |
Returns: the current text in the field, as a str, updated live as the user types.
Raises: ValueError if debounce is negative or label_visibility is invalid.
How it works
Streamlit's built-in st.text_input() only pushes a new value to Python on blur or Enter, because it listens to the browser's change event. streamlit-live-search instead:
- Listens to the native
inputevent on every keystroke (notchange). - Calls
Streamlit.setComponentValue()immediately (or afterdebouncems), which triggers a Streamlit script rerun with the new value. - Keeps the input's own React state in sync so the field never stutters or loses characters while a rerun is in flight.
Development guide
Prerequisites
- Python 3.8+
- Node.js 18+ and npm
Project layout
streamlit-live-search/
├── pyproject.toml
├── README.md
├── LICENSE
├── MANIFEST.in
├── setup.py
├── streamlit_live_search/ # Python package
│ ├── __init__.py
│ ├── component.py # declares & wraps the component
│ └── frontend/build/ # compiled frontend (generated by `npm run build`)
├── frontend/ # React + TypeScript source
│ ├── package.json
│ ├── tsconfig.json
│ ├── vite.config.ts
│ ├── src/
│ │ ├── index.tsx
│ │ ├── App.tsx
│ │ └── LiveSearch.tsx
│ └── public/
├── examples/
│ └── app.py
└── tests/
└── test_component.py
Running in dev mode (hot reload)
In one terminal, start the frontend dev server:
cd frontend
npm install
npm run dev
In another terminal, tell the Python side to use the dev server instead of the built assets, then run the example app:
export STREAMLIT_LIVE_SEARCH_DEV=1 # Windows: set STREAMLIT_LIVE_SEARCH_DEV=1
pip install -e .
streamlit run examples/app.py
Building for production
cd frontend
npm install
npm run build # outputs to streamlit_live_search/frontend/build
cd ..
pip install .
Linting & formatting
# Frontend
cd frontend
npm run lint
npm run format
# Python
ruff check .
black .
mypy streamlit_live_search
Testing
# Python
pytest
# Frontend
cd frontend
npm test
Publishing guide
Publishing the frontend build
The compiled frontend (streamlit_live_search/frontend/build/) must exist and be up to date before building the Python distribution — it's bundled as package data.
cd frontend
npm install
npm run build
cd ..
Building the Python distribution
python -m pip install --upgrade build twine
python -m build # creates dist/*.whl and dist/*.tar.gz
Publishing to PyPI
twine check dist/*
twine upload dist/* # production PyPI
# or, to test first:
twine upload --repository testpypi dist/*
Versioning
Bump the version in both pyproject.toml ([project].version) and streamlit_live_search/__init__.py (__version__) before each release, following SemVer.
Browser support
Chrome, Firefox, Edge, and Safari (latest two major versions).
Accessibility
- The input has
role="searchbox"and anaria-labelderived fromlabel. - The clear button has an explicit
aria-label="Clear search". - Helper text is linked via
aria-describedby. - The field is fully keyboard operable:
Tabto focus, type to search,Escto clear,Ctrl/Cmd+Kto jump focus to the field from anywhere on the page.
Contributing
Issues and pull requests are welcome. Please run the lint, format, and test commands above before submitting a PR.
License
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 streamlit_live_search_bidhan-1.0.0.tar.gz.
File metadata
- Download URL: streamlit_live_search_bidhan-1.0.0.tar.gz
- Upload date:
- Size: 359.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e7f84f8d4478e27ec0518fdf4e579d00e8360e43419c54d8509bca392c757aaf
|
|
| MD5 |
0401844bdba211e52868a89a5c0a0b57
|
|
| BLAKE2b-256 |
fb11764248a5489c1f760650371da63431d1c209ba4cf6871ba61be5577c1830
|
File details
Details for the file streamlit_live_search_bidhan-1.0.0-py3-none-any.whl.
File metadata
- Download URL: streamlit_live_search_bidhan-1.0.0-py3-none-any.whl
- Upload date:
- Size: 360.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af03f660618035a5281499f257ff734fb7fb19890a9428d78880d58cd97a5288
|
|
| MD5 |
f280c39b26f241169656b560d9373c8c
|
|
| BLAKE2b-256 |
99657687e3b5ed3a24100f4b763e5543aff0fe0a6f8e1eb73e22617d0abd5343
|