DataLens

Input widgets (button, slider, selectbox, text_input)

Let users filter and explore data with selectboxes, sliders, multiselects, text and number inputs, and wire them into pandas filters.

Level: Foundations · 8 min read

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

The idea

A dashboard becomes an app when the reader can ask their own questions: "just the Coffee category", "orders above Rp 500.000", "only the app channel". Each widget is one question, and thanks to the rerun model, using its answer is ordinary Python: read the value, filter the DataFrame, show the result.

import pandas as pd

sales = pd.read_csv("sales.csv")
completed = sales[sales["status"] == "completed"]

Choosing one option: selectbox and radio

The first thing most dashboard users want is to look at one slice of the data, like a single product category. A selectbox gives them a dropdown of choices, and whatever they pick is simply the return value, ready to use in a filter:

import streamlit as st
import pandas as pd

sales = pd.read_csv("sales.csv")
completed = sales[sales["status"] == "completed"]

categories = sorted(completed["category"].unique())
category = st.selectbox("Category", categories)
rows = completed[completed["category"] == category]
st.metric(f"{category} revenue", f"Rp {rows['revenue'].sum():,}")
st.dataframe(rows[["order_date", "product", "quantity", "revenue"]], hide_index=True)

To offer "everything" as a choice, add it to the options:

import streamlit as st

options = ["All"] + ["Coffee", "Electronics", "Home"]
choice = st.radio("Show", options, horizontal=True)
st.write("You picked", choice)

Choosing several: multiselect

Sometimes users want more than one option at a time, like both sales channels or just one of them. A multiselect lets them pick any number:

import streamlit as st
import pandas as pd

sales = pd.read_csv("sales.csv")
completed = sales[sales["status"] == "completed"]

channels = st.multiselect("Channels", ["web", "app"], default=["web", "app"])
rows = completed[completed["channel"].isin(channels)]
st.write(f"{rows['order_id'].nunique()} orders from {', '.join(channels) or 'no channel'}")

A multiselect returns a list. isin turns it straight into a filter. Note the empty case: if the user removes every channel, the list is empty and so is the result. Decide what that should show.

Numbers: slider and number_input

For a numeric filter, like "only show order lines worth at least this much", a slider is quicker to use than typing a number:

import streamlit as st
import pandas as pd

sales = pd.read_csv("sales.csv")
completed = sales[sales["status"] == "completed"]

min_revenue = st.slider("Minimum line revenue (Rp)", 0, 1_000_000, 100_000, step=50_000)
big = completed[completed["revenue"] >= min_revenue]
st.write(f"{len(big)} order lines of at least Rp {min_revenue:,}")

st.slider(label, min, max, default, step=...). Give it a tuple as the default and it becomes a range slider returning (low, high):

import streamlit as st

low, high = st.slider("Price range (thousand Rp)", 0, 400, (50, 200))
st.write(f"Between {low} and {high}")

st.number_input is a box with + and − buttons, better when users know the exact number (a quantity, a target).

Text: text_input

A search box lets users find products by name without scrolling through the whole table:

import streamlit as st
import pandas as pd

sales = pd.read_csv("sales.csv")
search = st.text_input("Search products", placeholder="e.g. coffee")
if search:
    hits = sales[sales["product"].str.contains(search, case=False)]
    st.write(f"{hits['product'].nunique()} matching products")
    st.dataframe(hits[["product", "category"]].drop_duplicates(), hide_index=True)

An empty text box returns "", so if search: only filters once the user types. case=False makes the search case-insensitive.

Keys: naming widgets

Every widget is identified by its label. Two widgets with the same label confuse Streamlit:

import streamlit as st

start = st.date_input("Date")
end = st.date_input("Date")

Streamlit stops with an error: There are multiple identical st.date_input widgets with the same generated key. Give each one a unique key= (and better labels):

import streamlit as st

start = st.date_input("From", key="start_date")
end = st.date_input("To", key="end_date")

Keys become essential with st.session_state, two modules from now.

Combining filters

Real apps chain several widgets into one filtered DataFrame:

import streamlit as st
import pandas as pd

sales = pd.read_csv("sales.csv")
completed = sales[sales["status"] == "completed"]

category = st.selectbox("Category", ["All"] + sorted(completed["category"].unique()))
channels = st.multiselect("Channels", ["web", "app"], default=["web", "app"])

rows = completed[completed["channel"].isin(channels)]
if category != "All":
    rows = rows[rows["category"] == category]

st.metric("Revenue", f"Rp {rows['revenue'].sum():,}")
st.dataframe(rows, hide_index=True)

Read inputs first, filter step by step, then display. When the filter logic grows, move it into a function filter_sales(df, category, channels) that you can test without Streamlit.

Why this matters for clean code

Widgets are user input, so all the input rules apply: give clear labels, derive options from the data, handle the empty case, and keep the filtering logic in plain functions. An app whose filters live in one tested function is easy to change; an app with filters scattered between widget calls isn't.

Recap

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

Next: Layout: columns, tabs, sidebar, expander →