An app that only displays information is a dashboard. An app that lets users interact with it — adjusting parameters, filtering data, uploading files — is a tool. Widgets are what make the difference.

Streamlit makes adding widgets incredibly simple. A single line of Python adds a slider, a dropdown or a text box, and Streamlit handles all the HTML, CSS and JavaScript for you. The widget returns its current value as a Python variable, and you use that value to control everything else in your app.

This guide covers every widget you will need, how they work together, and the important concept of reruns that makes Streamlit tick.

Download the Complete Code A ready-to-run Streamlit app demonstrating every widget in this guide — slider, selectbox, forms, sidebar and more.
Python script  ·  Full app  ·  Free

How Reruns Work — The Key to Understanding Streamlit

Before widgets make sense, you need to understand one fundamental thing about how Streamlit runs your code.

Every time a user interacts with any widget, Streamlit reruns your entire script from top to bottom. This is different from how most web frameworks work. There are no event handlers or callback functions. Your script just runs again, and the widgets return their new values.

This means writing interactive apps is simple: just use the widget's return value in your code, and every time the user changes the widget, the code that depends on it automatically uses the new value.

Python — the rerun model in one simple example
import streamlit as st # Every time the slider moves, this entire script reruns # st.slider() returns the current value of the slider age = st.slider('How old are you?', min_value=0, max_value=100, value=25) # This line uses the slider value — it updates automatically every rerun st.write(f'You selected: {age} years old') if age < 18: st.info('You are a minor.') elif age < 65: st.success('You are a working-age adult.') else: st.warning('You are a senior citizen.')
ℹ️ Run any Streamlit app with: streamlit run your_app.py from the terminal. It opens automatically in your browser at localhost:8501.

Widget Overview

Here is every widget covered in this guide at a glance:

st.text_input()
Single line of text
returns: str
st.text_area()
Multi-line text box
returns: str
st.number_input()
Numeric value with arrows
returns: int or float
st.slider()
Drag handle on a track
returns: int, float or tuple
st.selectbox()
Pick one from a dropdown
returns: any
st.multiselect()
Pick multiple from a list
returns: list
st.radio()
Pick one from radio buttons
returns: any
st.checkbox()
Tick box on or off
returns: bool
st.button()
Click to trigger action
returns: bool
st.date_input()
Calendar date picker
returns: date
st.time_input()
Time picker
returns: time
st.file_uploader()
Upload files from disk
returns: UploadedFile
st.color_picker()
Visual colour selector
returns: str (hex)

Text Input

st.text_input() shows a single-line text box. The first argument is the label shown above it. It returns whatever the user has typed as a string. If nothing is typed yet, it returns an empty string or the default value you set.

Python — st.text_input() with all common options
import streamlit as st # Basic text input name = st.text_input('Enter your name') # With a default value city = st.text_input('City', value='Bengaluru') # With placeholder text (shown when box is empty) email = st.text_input('Email address', placeholder='you@example.com') # Password input — hides the text as the user types password = st.text_input('Password', type='password') # Use the returned values if name: st.write(f'Hello, {name}! You are from {city}.') else: st.write('Please enter your name above.')

Text Area — Multi-line Input

Use st.text_area() when you need the user to type a longer piece of text — a description, a message, a block of code to analyse. It works identically to text_input but renders as a multi-line box.

Python — st.text_area() for longer text input
# height controls the initial size in pixels description = st.text_area( 'Describe your project', placeholder='Tell us what you are building...', height=150 ) if description: word_count = len(description.split()) st.caption(f'{word_count} words')

Number Input

st.number_input() shows a text box with up/down arrows for numeric input. It is better than a text box for numbers because it validates that the user cannot type letters and it adds convenient increment/decrement buttons.

Python — st.number_input() for integers and floats
# Integer input quantity = st.number_input( 'Quantity', min_value=1, max_value=100, value=1, step=1 ) # Float input — step controls decimal precision budget = st.number_input( 'Budget (USD)', min_value=0.0, value=500.0, step=50.0, format='%.2f' ) st.write(f'Total cost: ${quantity * budget:,.2f}')

Slider

The slider is one of the most satisfying widgets to use. It lets users drag a handle along a track to pick a number. It is great for parameters like age, year, temperature or any value within a known range where typing feels unnecessary.

Python — st.slider() for numbers, floats and dates
# Integer slider year = st.slider('Select a year', min_value=2000, max_value=2024, value=2020) # Float slider with step confidence = st.slider( 'Confidence threshold', min_value=0.0, max_value=1.0, value=0.5, step=0.05 ) # Date slider from datetime import date selected_date = st.slider( 'Select a date', min_value=date(2020, 1, 1), max_value=date.today(), value=date(2022, 6, 1) ) st.write(f'Year: {year} | Confidence: {confidence} | Date: {selected_date}')

Range Slider — Select a Min and Max

Pass a tuple as the value parameter and you get a range slider with two handles. The widget returns a tuple of two values — the lower and upper bounds of the selected range. This is perfect for filtering data.

Python — range slider for filtering a dataset
import pandas as pd # value=(min, max) creates a range slider with two handles price_range = st.slider( 'Price range (USD)', min_value=0, max_value=10000, value=(500, 5000), # tuple = range slider step=100 ) min_price, max_price = price_range st.write(f'Showing products from ${min_price:,} to ${max_price:,}') # Use the range to filter a dataframe # df_filtered = df[(df['price'] >= min_price) & (df['price'] <= max_price)]

Selectbox — Pick One From a Dropdown

st.selectbox() shows a dropdown where the user picks exactly one option. You pass a list of options and it returns whichever one the user selected. The options can be strings, numbers or any Python objects.

Python — st.selectbox() with strings and objects
# Basic selectbox country = st.selectbox( 'Select your country', options=['India', 'USA', 'UK', 'Germany', 'Japan'] ) # With a default selection (by index) model = st.selectbox( 'Choose ML model', options=['Linear Regression', 'Random Forest', 'XGBoost', 'Neural Network'], index=1 # 'Random Forest' is selected by default ) # Selectbox over a pandas DataFrame column import pandas as pd df = pd.read_csv('data.csv') selected = st.selectbox('Choose a column', df.columns) st.write(f'You chose: {country}') st.write(f'Model selected: {model}')

Multiselect — Pick Several Options

st.multiselect() lets the user pick as many options as they want. It returns a list of the selected items. If nothing is selected, it returns an empty list.

Python — st.multiselect() for filtering with multiple values
languages = st.multiselect( 'Which languages do you know?', options=['Python', 'JavaScript', 'Java', 'Go', 'Rust', 'TypeScript'], default=['Python'] # pre-selected options ) if languages: st.write(f'You know {len(languages)} language(s): {", ".join(languages)}') else: st.warning('Please select at least one language.') # Use multiselect to filter a DataFrame # df_filtered = df[df['language'].isin(languages)]

Radio Buttons — Pick One Visually

st.radio() shows all options at once as radio buttons rather than hiding them in a dropdown. Use it when you have a small number of choices (3-5) and you want all options to be immediately visible.

Python — st.radio() for a small set of visible choices
# Vertical radio (default) chart_type = st.radio( 'Chart type', options=['Bar chart', 'Line chart', 'Scatter plot'] ) # Horizontal radio — better when options are short theme = st.radio( 'Theme', options=['Light', 'Dark', 'System'], horizontal=True ) st.write(f'Showing {chart_type} in {theme} mode')

Checkbox — On or Off

st.checkbox() returns True when ticked and False when not. It is perfect for toggling sections of your app on and off, showing or hiding data, and enabling optional features.

Python — st.checkbox() to toggle sections and options
import pandas as pd # Show or hide a section based on checkbox show_raw_data = st.checkbox('Show raw data') if show_raw_data: df = pd.DataFrame({'A': [1,2,3], 'B': [4,5,6]}) st.dataframe(df) # With a default state (checked by default) include_tax = st.checkbox('Include tax in total', value=True) price = 1000 total = price * 1.18 if include_tax else price st.metric('Total', f'${total:,.2f}')

Button — Trigger an Action

st.button() returns True only on the rerun triggered by the click itself. On the next rerun it goes back to False. This means button clicks are momentary — use them to trigger one-time actions like submitting data or running a calculation, not for toggles (use checkbox for that).

Python — st.button() and st.download_button()
# Basic button if st.button('Run analysis'): st.success('Analysis complete!') st.balloons() # Styled button variants st.button('Primary action', type='primary') # blue, prominent st.button('Secondary action') # grey, subtle # Download button — triggers a file download csv_data = 'name,score\nAlice,95\nBob,87' st.download_button( label='Download results as CSV', data=csv_data, file_name='results.csv', mime='text/csv' )
⚠️ Button state resets every rerun. Do not use st.button() to toggle something visible in the UI — because on the next user interaction the button will be False again and the thing will disappear. Use st.checkbox() or st.session_state for persistent toggles.

Date and Time Pickers

Python — date input, time input and date range
from datetime import date, time # Single date picker birthday = st.date_input( 'Date of birth', value=date(1995, 6, 15), min_value=date(1900, 1, 1), max_value=date.today() ) # Date range — returns a tuple of (start_date, end_date) date_range = st.date_input( 'Select a date range', value=(date(2024, 1, 1), date(2024, 12, 31)) ) if len(date_range) == 2: start, end = date_range st.write(f'{(end - start).days} days selected') # Time picker meeting_time = st.time_input( 'Meeting time', value=time(9, 30), step=900 # step in seconds — 900 = 15 minute intervals )

File Uploader

st.file_uploader() lets users upload files directly from their computer into your app. It returns an UploadedFile object (or None if no file has been uploaded yet). The object behaves like a file — you can read it, pass it to pandas, PIL or any other library.

Python — uploading CSV and image files
import pandas as pd from PIL import Image # Upload a single CSV file uploaded_csv = st.file_uploader( 'Upload a CSV file', type=['csv'] ) if uploaded_csv is not None: df = pd.read_csv(uploaded_csv) st.write(f'Loaded {df.shape[0]} rows and {df.shape[1]} columns') st.dataframe(df.head()) # Upload an image uploaded_img = st.file_uploader( 'Upload an image', type=['jpg', 'jpeg', 'png'] ) if uploaded_img is not None: img = Image.open(uploaded_img) st.image(img, caption='Uploaded image', use_column_width=True) # Accept multiple files at once files = st.file_uploader('Upload multiple files', accept_multiple_files=True) st.write(f'{len(files)} file(s) uploaded')

Colour Picker

st.color_picker() opens a colour selector and returns the chosen colour as a hex string like #FF5733. You can use this directly in any matplotlib or plotly chart.

Python — colour picker for chart customisation
colour = st.color_picker('Pick a chart colour', value='#ffb800') st.write(f'Selected: {colour}') # Use the hex colour in a matplotlib chart import matplotlib.pyplot as plt import numpy as np fig, ax = plt.subplots() ax.bar(['A', 'B', 'C'], [3, 7, 5], color=colour) st.pyplot(fig)

Forms — Batch Multiple Inputs

Normally Streamlit reruns every time any widget changes. If you have ten widgets and a slow computation, this means the app reruns ten times as the user fills in the form — which is terrible for performance.

Forms solve this. Widgets inside a form do not trigger a rerun as the user changes them. The rerun only happens when the user clicks the submit button. All widget values are collected and processed together in one single rerun.

Python — wrapping widgets in a form to batch submissions
with st.form('user_profile'): st.subheader('Create your profile') name = st.text_input('Full name') age = st.slider('Age', 18, 90, 25) role = st.selectbox('Role', ['Developer', 'Designer', 'Manager']) skills = st.multiselect('Skills', ['Python', 'SQL', 'ML', 'React']) bio = st.text_area('Short bio', height=80) # submit_button triggers the rerun submitted = st.form_submit_button('Save profile', type='primary') # This block runs once when the form is submitted if submitted: if not name: st.error('Name is required.') else: st.success(f'Profile saved for {name}!') st.json({'name':name, 'age':age, 'role':role, 'skills':skills})

Widgets in Columns — Side by Side

By default every widget takes the full width. Use st.columns() to place widgets side by side and make better use of horizontal space.

Python — placing widgets side by side with columns
# Equal width columns col1, col2, col3 = st.columns(3) with col1: first_name = st.text_input('First name') with col2: last_name = st.text_input('Last name') with col3: age = st.number_input('Age', 1, 120, 25) # Unequal column widths — [2,1] means left is twice as wide as right c_main, c_side = st.columns([2, 1]) with c_main: query = st.text_input('Search', placeholder='Type to search...') with c_side: sort = st.selectbox('Sort by', ['Name', 'Date', 'Score'])

Full Example App — Data Explorer

Here is a complete working Streamlit app that uses most of the widgets from this guide together to build a simple data explorer:

Python — complete data explorer app using multiple widgets
import streamlit as st import pandas as pd import numpy as np st.title('Sales Data Explorer') # ── Sidebar controls ────────────────────────────────── with st.sidebar: st.header('Filters') region = st.multiselect( 'Region', ['North', 'South', 'East', 'West'], default=['North', 'South'] ) revenue_range = st.slider( 'Revenue range (USD)', 0, 100000, (10000, 80000), 5000 ) chart_type = st.radio( 'Chart type', ['Bar', 'Line'], horizontal=True ) show_table = st.checkbox('Show data table', value=True) # ── Generate dummy data ─────────────────────────────── np.random.seed(42) df = pd.DataFrame({ 'Month': ['Jan','Feb','Mar','Apr','May','Jun'], 'Revenue': np.random.randint(20000, 90000, 6), 'Region': np.random.choice(['North','South','East','West'], 6) }) # ── Apply filters ───────────────────────────────────── filtered = df[ df['Region'].isin(region) & (df['Revenue'] >= revenue_range[0]) & (df['Revenue'] <= revenue_range[1]) ] # ── Show metrics ────────────────────────────────────── m1, m2, m3 = st.columns(3) m1.metric('Rows shown', len(filtered)) m2.metric('Total revenue', f"${filtered['Revenue'].sum():,}") m3.metric('Avg revenue', f"${filtered['Revenue'].mean():,.0f}") # ── Chart ───────────────────────────────────────────── if not filtered.empty: if chart_type == 'Bar': st.bar_chart(filtered.set_index('Month')['Revenue']) else: st.line_chart(filtered.set_index('Month')['Revenue']) else: st.warning('No data matches your filters.') # ── Conditional table ───────────────────────────────── if show_table: st.dataframe(filtered, use_container_width=True)

Quick Reference

WidgetReturnsUse When
st.text_input()strShort free text — names, emails, search queries
st.text_area()strMulti-line text — descriptions, comments, code
st.number_input()int or floatExact numeric value with precise control
st.slider()number or tupleValue within a known range — age, year, threshold
st.selectbox()anyPick exactly one from a list (more than 5 options)
st.multiselect()listPick any number from a list — filters, tags
st.radio()anyPick exactly one — 2 to 5 options, all visible
st.checkbox()boolToggle something on/off persistently
st.button()boolOne-time actions — run, submit, reset
st.date_input()date or tupleDate selection or date range
st.file_uploader()UploadedFileLoad CSV, images or any file from user's disk
st.form()Group widgets — submit all values together

⚡ Key Takeaways
  • Every widget interaction triggers a full rerun of your script from top to bottom. This is Streamlit's core model — there are no event handlers. Just use the widget's return value and everything updates automatically.
  • Every widget returns a Python value you store in a variable. st.slider() returns a number, st.text_input() returns a string, st.multiselect() returns a list.
  • Use st.text_input() for short free text (name, email, search). Use st.text_area() when you need paragraph-length input.
  • Use st.slider() with a tuple value to create a range slider — perfect for filtering data by a min/max range. The return value is a tuple you can unpack.
  • Use st.selectbox() when the user picks exactly one option from a longer list. Use st.radio() when the list is short (2-5) and you want all options visible at once.
  • st.checkbox() returns True/False and keeps its state across reruns — good for toggling sections. st.button() is True only on the click rerun — good for one-time actions like running a model.
  • Move your controls to the sidebar with with st.sidebar: to keep the main area clean for charts and results. Use st.columns() to place widgets side by side.
  • Wrap slow or complex inputs in a st.form() block. Widgets inside a form do not trigger reruns until the user clicks submit — preventing unnecessary computation as the user fills in the form.
  • st.file_uploader() returns an UploadedFile object that behaves like a regular file. Pass it directly to pd.read_csv(), PIL.Image.open() or any library that accepts file objects.
  • Run your app with streamlit run app.py in the terminal. It opens in your browser at localhost:8501 and hot-reloads whenever you save the file.
Download the Complete Code A ready-to-run Streamlit app with all widgets from this guide — slider, selectbox, forms, sidebar, file uploader and more.
Python script  ·  Full app  ·  Free