Streamlit · 03 — Widgets: Making Your App Interactive¶
Time: ~60 min · CLOs: CLO-1, CLO-3 · Level: Beginner Prerequisite: tutorial (document 2) — you know the rerun model.
1. The widget rule¶
value = st.widget_name(label, options..., default...)
Every widget is one line that returns a value. The variable is the widget. The script re-runs whenever the user changes it, and the variable holds the new value. There is no event system, no callbacks — just reruns and variables.
2. The widget catalogue¶
Buttons and toggles¶
if st.button("Run analysis"): # True only on the rerun caused by the click
st.write("Analysis running...")
checked = st.checkbox("Show details") # True/False
if checked:
st.write("Details are shown.")
st.button — one-shot action. st.checkbox — persistent on/off state.
Choosing from options¶
day = st.selectbox("Pick a day", ["Mon", "Tue", "Wed"]) # one choice
days = st.multiselect("Pick days", ["Mon", "Tue", "Wed"], default=["Mon"])
# multi returns a LIST of the chosen values
st.selectbox — a dropdown; returns the selected item. st.multiselect —
a checkbox list; returns a list (possibly empty!). Handle the empty
case, or filter logic breaks.
Numbers¶
price = st.slider("Price", min_value=0, max_value=100, value=50, step=5)
age = st.number_input("Age", min_value=0, max_value=120, value=18, step=1)
st.slider — drag to choose, good for ranges. st.number_input — type a
number (or use arrows), good for precise values.
Text¶
name = st.text_input("Name") # one line of text
question = st.text_area("Question") # several lines
Both return strings. An empty input returns "" (falsy) — check if name:
before using it.
Files¶
uploaded = st.file_uploader("Upload a CSV", type=["csv"])
if uploaded is not None:
import pandas as pd
df = pd.read_csv(uploaded) # pandas reads it directly!
st.write(df.head())
st.file_uploader gives you a file-like object pandas can read. This is
how users bring their data into your app.
3. Quick reference — what each widget returns¶
| Widget | Returns | Typical use |
|---|---|---|
st.button |
True/False (one rerun) |
trigger an action |
st.checkbox |
True/False |
show/hide a section |
st.selectbox |
the chosen item | pick one category |
st.multiselect |
a list of items | pick several categories |
st.slider |
a number (or tuple for range) | continuous value |
st.number_input |
a number | precise value |
st.text_input |
a string | short text |
st.text_area |
a string | long text |
st.file_uploader |
file-like or None |
load a file |
Example 2 — Interactive Calculator¶
A complete calculator app using number_input, selectbox, and button.
Explanation¶
Three widgets in: two numbers and an operation. The app stores the chosen operation and numbers as variables (widget rule), then performs the calculation with plain Python. A button controls when the calculation happens so the result doesn't flash during typing. This is the pattern behind every "run my analysis" button in a data app.
Code¶
Save as app.py:
import streamlit as st
st.title("🧮 Interactive Calculator")
st.markdown("Pick two numbers and an operation, then press **Calculate**.")
# 1) Widgets -> variables
num1 = st.number_input("First number", value=0.0, step=1.0)
num2 = st.number_input("Second number", value=0.0, step=1.0)
operation = st.selectbox("Operation", ["+", "-", "*", "/"])
# 2) Guard against division by zero BEFORE calculating
if operation == "/" and num2 == 0:
st.error("Cannot divide by zero — change the second number.")
st.stop() # stop the script here
# 3) Calculate with plain Python
if st.button("Calculate"):
if operation == "+":
result = num1 + num2
elif operation == "-":
result = num1 - num2
elif operation == "*":
result = num1 * num2
else:
result = num1 / num2
# 4) Show the result nicely
st.success(f"{num1} {operation} {num2} = **{result:,.2f}**")
Expected result¶
- Title and instruction text at the top.
- Two number boxes (default
0), an operation dropdown, a Calculate button. - Pick
5and3, choose*, click Calculate →5 * 3 = 15.00in a green success box. - Choose
/and set the second number to0→ a red error message appears immediately, and the app stops before the button section.
Exercise¶
- Add a fourth widget:
st.checkbox("Show the calculation steps")that printsnum1,num2, and the operation in an expander (st.expander("Steps")). - Handle the power operation
**as a fifth choice in the selectbox. - Format the result to a whole number when both inputs are integers
(hint:
if num1 == int(num1)).
Challenge¶
Turn the calculator into a grade calculator: number_input marks for
three assessments (Quiz 1, Quiz 2, Final), each with a weight
(st.slider 0–100 for each), and the app computes the weighted total and
shows st.success (pass ≥ 50) or st.error (fail). Use st.progress(...)
to visualize the total out of 100.
Common widget mistakes¶
| Mistake | Fix |
|---|---|
multiselect result used as a single value |
it returns a list — loop over it or check if days: |
| Division by zero crashes the app | check before calculating, st.error + st.stop() |
| Text input used before it has a value | if name: guard — empty strings are falsy |
| Widgets with the same label | each widget needs a unique label (or a key=) |
| Button "does nothing" | buttons are True for one rerun — put the action inside the if |
| File uploader crashes on non-CSV | check the file extension or wrap pd.read_csv in try/except |
Checkpoint questions¶
- What does
st.multiselectreturn when nothing is selected? (CLO-1 · Understand · Easy) Answer: an empty list[]. - Why does
st.buttonbehave differently fromst.checkbox? (CLO-1 · Understand · Medium) Answer: the button isTrueonly for the single rerun caused by the click; the checkbox keeps its state across reruns until the user changes it. - How do you stop an app gracefully when the user's input is invalid?
CLO-1 · Apply · Medium
Answer: show
st.error(...)and callst.stop().