Claude Streamlit skill: 10 data apps you can ship
Ten real Streamlit apps — a multi-page dashboard, a cached report, a session-state wizard, an upload-to-chart pipeline, an interactive Plotly view, an LLM chat UI, an editable database table, an auth gate, a fragment-refreshed panel, and a deploy config — each as a single Claude prompt with the exact Streamlit Python it writes.
Streamlit reruns your whole script on every click. That one fact is behind most of what people get wrong — slow apps, lost state, stuttering forms. The cookbook below is organised around the features that tame it: @st.cache_data, st.session_state, st.form, and @st.fragment. Get those right and Streamlit stays the fastest way to turn a Python script into a shareable app.
Already know what skills are? Skip to the cookbook. First time? Read the explainer then come back. Need the install? It’s on the /skills/streamlit page.

On this page · 21 sections▾
- What this skill does
- The cookbook
- Install + README
- Watch it built
- 01 · Multi-page sales dashboard scaffold
- 02 · Cache an expensive load with st.cache_data
- 03 · Session-state wizard form
- 04 · File-upload to pandas to chart pipeline
- 05 · Interactive Plotly chart with cross-filters
- 06 · LLM chat UI with st.chat_message
- 07 · Editable st.data_editor writing back to a DB
- 08 · Secrets and an OpenID auth gate
- 09 · Partial reruns with st.fragment
- 10 · Deploy config for Community Cloud and Docker
- Community signal
- The contrarian take
- Real apps shipped
- Gotchas
- Pairs well with
- FAQ
- Sources
What this skill actually does
Sixty seconds of context before the cookbook — what the Streamlit skill writes when you invoke it, what it’s grounded in, and the one thing it does NOT do for you.
What this skill actually does
“When working with Streamlit web apps, data dashboards, ML/AI app UIs, interactive Python visualizations, or building data science applications with Python.”
— sverzijl, the skill author · /skills/streamlit
What Claude returns
When triggered, Claude writes idiomatic Streamlit Python — an `app.py` that imports `streamlit as st`, lays out widgets (`st.button`, `st.slider`, `st.file_uploader`), draws data with `st.dataframe` / `st.line_chart` / `st.plotly_chart`, manages state through `st.session_state`, and caches expensive work behind `@st.cache_data` and `@st.cache_resource`. It is grounded in 317 pages of official docs split across eight reference files (api.md, concepts.md, tutorials.md, deployment.md), so it reaches for `st.fragment`, `st.data_editor`, multi-page `st.navigation`, and `st.login` when the task calls for them — not just the basics.
What it does NOT do
It does not install Streamlit or run the server for you — `pip install streamlit` and `streamlit run app.py` are still yours. It writes the code; you host it.
How you trigger it
Build me a multi-page sales dashboard from this CSV.Add an st.session_state wizard form to my app.Why does my Streamlit app refetch on every slider change?Cost when idle
~100 tokens at idle (the skill name + description in the system prompt). The eight reference files load only when the skill is triggered.
The cookbook
Each entry below is an app you could ship this week. They run roughly in order of Streamlit depth — the early ones cover layout and the caching that fixes a slow app, the middle ones lean on state, charts, and chat, and the later ones reach for st.data_editor, auth, fragments, and deployment. Every entry pairs with one or two skills or MCP servers you already have on mcp.directory.
Install + README
If the skill isn’t on your machine yet, here’s the one-liner. The full install panel (Codex, Copilot, Antigravity variants) is on the skill page — the same UI’s embedded below.
One-line install · by sverzijl
Open skill pageInstall
mkdir -p .claude/skills/streamlit && curl -L -o skill.zip "https://mcp.directory/api/skills/download/412" && unzip -o skill.zip -d .claude/skills/streamlit && rm skill.zipInstalls to .claude/skills/streamlit
Watch it built
The official Streamlit channel building a full dashboard app in under 200 lines. Worth eight minutes before the cookbook because it shows the rerun model in motion — widgets at the top, the script flowing down, the page redrawing on each interaction.
Multi-page sales dashboard scaffold
A multi-page app using the st.Page / st.navigation router: an Overview page with KPI metrics, a Trends page with a time-series chart, and a Detail page with a filterable table. Shared data loads once and every page reads from it.
ForAnalysts who keep rebuilding the same dashboard from a fresh notebook each quarter.
The prompt
Scaffold a 3-page Streamlit app from `data/sales.csv` (columns: date, region, rep, product, amount). Use `st.navigation` with `st.Page` for Overview / Trends / Detail. Put a `load_data()` function behind `@st.cache_data` in a shared module so all three pages reuse it. Overview shows four `st.metric` cards (total revenue, deals, avg deal size, MoM delta). Trends shows `st.line_chart` of revenue by month. Detail shows a `st.dataframe` filtered by a `st.sidebar` region selectbox. Call `st.set_page_config(layout='wide')` once.What slides.md looks like
# app.py
import streamlit as st
st.set_page_config(page_title="Sales", layout="wide")
pages = [
st.Page("views/overview.py", title="Overview", icon=":material/dashboard:"),
st.Page("views/trends.py", title="Trends", icon=":material/trending_up:"),
st.Page("views/detail.py", title="Detail", icon=":material/table:"),
]
st.navigation(pages).run()
# data.py
import streamlit as st, pandas as pd
@st.cache_data
def load_data():
return pd.read_csv("data/sales.csv", parse_dates=["date"])One-line tweak
Swap `st.navigation(pages)` for `st.navigation({'Reports': [...], 'Admin': [...]})` to get grouped section headers in the sidebar nav.
Cache an expensive load with st.cache_data
A report app whose data pull takes 8 seconds. Wrap it in @st.cache_data with a ttl so the first run pays the cost and every widget interaction afterward is instant — the rerun model's single biggest trap, solved.
ForAnyone whose Streamlit app feels slow because it refetches on every click.
The prompt
My app re-runs a slow `fetch_orders()` (a 2,000-row API pull) on every slider change, so the UI stutters. Refactor it: put `fetch_orders()` behind `@st.cache_data(ttl=600)` so it caches for ten minutes and reruns only when its arguments change. Add a 'Refresh data' button that calls `st.cache_data.clear()` then `st.rerun()`. Explain in a comment why the model loading must use `@st.cache_resource` instead of `@st.cache_data`.What slides.md looks like
import streamlit as st
@st.cache_data(ttl=600) # cached value, re-run on arg change
def fetch_orders(region: str):
return api.get_orders(region) # the slow 8s call
@st.cache_resource # one shared object, never copied
def get_db():
return create_engine(DB_URL)
region = st.selectbox("Region", REGIONS)
orders = fetch_orders(region) # instant after first call
if st.button("Refresh data"):
st.cache_data.clear()
st.rerun()One-line tweak
Add `show_spinner='Loading orders…'` to the decorator so the first (uncached) run shows a labelled spinner instead of a blank pause.
Session-state wizard form
A 3-step onboarding wizard inside one app. st.session_state tracks the current step and carries answers forward; st.form batches each step's inputs so the app doesn't rerun on every keystroke.
ForBuilders who need a guided multi-step flow, not a single wall of inputs.
The prompt
Build a 3-step wizard. Keep `st.session_state.step` (default 0) and `st.session_state.answers` (a dict). Step 0: a `st.form` with name + email. Step 1: a `st.form` with company + team size (`st.number_input`). Step 2: a review screen that prints `st.session_state.answers` and a Submit button. Each form's submit advances `st.session_state.step` by 1 and stores values into `answers`. Add a Back button that decrements the step. Guard against rerun loss by initializing state at the top.What slides.md looks like
import streamlit as st
st.session_state.setdefault("step", 0)
st.session_state.setdefault("answers", {})
if st.session_state.step == 0:
with st.form("step0"):
name = st.text_input("Name")
email = st.text_input("Email")
if st.form_submit_button("Next"):
st.session_state.answers |= {"name": name, "email": email}
st.session_state.step = 1
st.rerun()
elif st.session_state.step == 2:
st.write(st.session_state.answers)
st.button("Submit", type="primary")One-line tweak
Render a `st.progress(st.session_state.step / 2)` bar above the form so the user sees how far through the wizard they are.
File-upload to pandas to chart pipeline
Drop a CSV on the page and get an instant profiled chart. st.file_uploader hands bytes to pandas, the skill picks numeric columns, and st.bar_chart renders — no schema known ahead of time.
ForTeams who want a 'paste your CSV, see a chart' internal tool.
The prompt
Build an upload-and-chart app. `st.file_uploader('Upload CSV', type='csv')` → read into a DataFrame with `pd.read_csv`. Show `st.dataframe(df.head())`. Auto-detect numeric columns with `df.select_dtypes('number')`; let the user pick an X column (`st.selectbox`) and one or more Y columns (`st.multiselect`). Render `st.line_chart(df, x=x_col, y=y_cols)`. Handle the empty state — if no file is uploaded, show `st.info('Upload a CSV to begin')` and `st.stop()`.What slides.md looks like
import streamlit as st, pandas as pd
up = st.file_uploader("Upload CSV", type="csv")
if up is None:
st.info("Upload a CSV to begin")
st.stop()
df = pd.read_csv(up)
st.dataframe(df.head())
nums = df.select_dtypes("number").columns.tolist()
x = st.selectbox("X axis", df.columns)
ys = st.multiselect("Y axis", nums, default=nums[:1])
if ys:
st.line_chart(df, x=x, y=ys)One-line tweak
Swap `pd.read_csv` for `pd.read_excel` and `type=['csv','xlsx']` to accept spreadsheets the same way.
Interactive Plotly chart with cross-filters
A Plotly figure that updates from sidebar filters. The skill reaches past the built-in charts to st.plotly_chart so you get hover tooltips, zoom, and a legend the user can toggle.
ForAnalysts who outgrew st.line_chart and need real interactivity.
The prompt
Build a revenue explorer with Plotly. Sidebar: a `st.multiselect` of regions and a `st.slider` date range. Filter the DataFrame on those, then build a `plotly.express` line chart of revenue over time, coloured by region. Render with `st.plotly_chart(fig, use_container_width=True)`. Add a `st.radio` to switch the chart between 'line' and 'area'. Keep the figure construction in a function so the cookbook reader can reuse it.What slides.md looks like
import streamlit as st
import plotly.express as px
regions = st.sidebar.multiselect("Region", REGIONS, default=REGIONS)
view = df[df.region.isin(regions)]
kind = st.radio("Chart", ["line", "area"], horizontal=True)
fig = (px.area if kind == "area" else px.line)(
view, x="date", y="amount", color="region",
)
fig.update_layout(margin=dict(l=0, r=0, t=10, b=0))
st.plotly_chart(fig, use_container_width=True)One-line tweak
Swap `import plotly.express as px` for `import altair as alt` and `st.altair_chart` if your team standardises on Altair — the skill covers both.
LLM chat UI with st.chat_message
A ChatGPT-style interface in about 25 lines. st.chat_input takes the prompt, st.chat_message renders the bubbles, and st.session_state.messages is the running history that survives reruns.
ForEngineers wiring a model behind a chat front end for a demo.
The prompt
Build a chat app. Keep history in `st.session_state.messages` (a list of {role, content}). On load, replay every message with `st.chat_message(role)`. Read new input from `st.chat_input('Ask…')`; append the user turn, then call my `generate(prompt, history)` function (leave it as a stub I'll wire to an API). Stream the assistant reply with `st.write_stream` so tokens appear live, and append the final text to history. Add a sidebar button that clears the conversation.What slides.md looks like
import streamlit as st
st.session_state.setdefault("messages", [])
for m in st.session_state.messages:
with st.chat_message(m["role"]):
st.markdown(m["content"])
if prompt := st.chat_input("Ask…"):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
with st.chat_message("assistant"):
reply = st.write_stream(generate(prompt, st.session_state.messages))
st.session_state.messages.append({"role": "assistant", "content": reply})One-line tweak
Wrap the model call in `@st.cache_data` keyed on the prompt to cache identical questions during a demo and avoid burning tokens on repeats.
Editable st.data_editor writing back to a DB
A spreadsheet-in-the-browser. st.data_editor renders an editable grid; the skill diffs the edited frame against the original and writes only the changed rows back to the database on Save.
ForOps teams who maintain a small reference table and hate raw SQL UPDATEs.
The prompt
Build an editable table over a `products` table (sku, name, price, in_stock). Load it with a cached `read_products()`. Render `st.data_editor(df, num_rows='dynamic', key='editor')`. On a 'Save changes' button, compare the editor's current value to the original DataFrame, compute the changed/added/deleted rows, and run the corresponding UPDATE / INSERT / DELETE through SQLAlchemy in a transaction. Show `st.toast('Saved N changes')`. Keep the DB engine behind `@st.cache_resource`.What slides.md looks like
import streamlit as st, pandas as pd
original = read_products() # cached
edited = st.data_editor(
original, num_rows="dynamic", key="editor",
column_config={"price": st.column_config.NumberColumn(format="$%.2f")},
)
if st.button("Save changes", type="primary"):
changed = edited.compare(original)
write_back(edited, original) # UPDATE/INSERT/DELETE
st.toast(f"Saved {len(changed)} rows")
st.cache_data.clear()One-line tweak
Add `disabled=['sku']` to `st.data_editor` so the primary key column is read-only and edits can't orphan a row.
Secrets and an OpenID auth gate
Lock the app behind a login. st.login / st.user gate the page using OpenID Connect, and credentials live in .streamlit/secrets.toml — never in the code the skill writes.
ForAnyone deploying an internal app that shouldn't be world-readable.
The prompt
Add a Google OpenID auth gate to my app. Use `st.user.is_logged_in`: if false, show only a title and a `st.login()` button via `st.button`, then `st.stop()`. If true, greet `st.user.name` and render the real app, with a `st.logout()` button in the sidebar. Read the API key the app needs from `st.secrets['api']['key']` rather than hardcoding it. Generate the matching `.streamlit/secrets.toml` stub with the `[auth]` block and placeholders — no real secrets in the file.What slides.md looks like
import streamlit as st
if not st.user.is_logged_in:
st.title("Internal Dashboard")
st.button("Log in with Google", on_click=st.login)
st.stop()
st.sidebar.button("Log out", on_click=st.logout)
st.write(f"Hello, {st.user.name}")
api_key = st.secrets["api"]["key"] # from .streamlit/secrets.toml
# .streamlit/secrets.toml
# [auth]
# redirect_uri = "http://localhost:8501/oauth2callback"
# cookie_secret = "REPLACE_ME"
# client_id = "REPLACE_ME"
# client_secret = "REPLACE_ME"
# server_metadata_url = "https://accounts.google.com/.well-known/openid-configuration"One-line tweak
Restrict access further by checking `st.user.email.endswith('@yourco.com')` after login and calling `st.error` + `st.stop()` for outside addresses.
Partial reruns with st.fragment
A dashboard with a live-updating panel that doesn't redraw the whole page. @st.fragment isolates the panel so its refresh — and only its refresh — reruns, keeping the rest of the app static and snappy.
ForBuilders whose full-page rerun is too heavy to run on a timer.
The prompt
My dashboard has a heavy header (charts, tables) and one 'live metrics' panel I want to auto-refresh every 5 seconds. Wrap the live panel in a function decorated with `@st.fragment(run_every='5s')` so only that fragment reruns on the timer — the expensive header stays put. Inside the fragment, pull the latest numbers and render three `st.metric` cards. Explain in a comment why moving the timer logic out of the fragment would force a full-page rerun.What slides.md looks like
import streamlit as st
st.title("Ops dashboard")
render_expensive_header() # runs once, not on the timer
@st.fragment(run_every="5s")
def live_panel():
stats = fetch_live_stats() # cheap call, every 5s
a, b, c = st.columns(3)
a.metric("Requests/s", stats.rps, stats.rps_delta)
b.metric("Error rate", f"{stats.err:.2%}")
c.metric("p95 latency", f"{stats.p95} ms")
live_panel() # only this reruns on the intervalOne-line tweak
Add a manual `st.button('Refresh now')` inside the fragment — clicking it reruns just the fragment, independent of the 5-second timer.
Deploy config for Community Cloud and Docker
The files that get the app off your laptop. The skill writes requirements.txt, a config.toml theme, and a production Dockerfile with the right health check — the deployment plumbing the docs cover but tutorials skip.
ForAnyone whose app runs locally but has never been deployed.
The prompt
Prepare my Streamlit app for deployment two ways. (1) Community Cloud: generate a `requirements.txt` pinned from my imports and a `.streamlit/config.toml` with a dark theme (`[theme] base='dark'`, a primaryColor) plus `[server] maxUploadSize=50`. (2) Docker: write a Dockerfile that installs deps, copies the app, exposes 8501, and sets the entrypoint to `streamlit run app.py --server.port=8501 --server.address=0.0.0.0`, with a `HEALTHCHECK` hitting `/_stcore/health`. Note which secrets move to the Community Cloud secrets UI versus a mounted file.What slides.md looks like
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8501
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
ENTRYPOINT ["streamlit", "run", "app.py", \
"--server.port=8501", "--server.address=0.0.0.0"]
# .streamlit/config.toml
[theme]
base = "dark"
primaryColor = "#FF4B4B"
[server]
maxUploadSize = 50One-line tweak
Add a `packages.txt` listing apt packages (e.g. `libgomp1`) when a dependency needs a system library Community Cloud doesn't ship by default.
Community signal
Three voices from people running Streamlit for real work. The first is the steady-use story — a year of internal apps — the second is the weekend-build speed that defines the tool, the third is the honest line about why people reach for it.
“Been using Streamlit at work for over a year. It's great for quickly building internal apps where latency or styling is not a concern (though it is a lot prettier than notebooks).”
everling · Hacker News
An HN user reporting a year-plus of real use of Streamlit for internal tools at work.
“I built a branching conversational LLM chat utility using streamlit in a weekend having never used it before.”
peddling-brink · Hacker News
A first-time user describing how fast a Streamlit LLM chat app comes together — exactly use case 6 below.
“I dig it for building something and not wanting to spend 1 million years on a front end.”
Mx-Mercedes · Reddit
A Reddit reply in the 'Is Streamlit really that good?' thread, summarising the core appeal in one line.
The contrarian take
Not everyone reaches for Streamlit at scale. The most cited critique on Hacker News, from Alyx1337, is about the rerun model under load:
“When your app has significant data or a significant model to work with or multiple pages or users, this approach fails, and the app starts freezing constantly.”
Alyx1337 · Hacker News
From the Hacker News thread on Streamlit's execution model — the full-script-rerun design under heavy data, models, or many users.
Fair, and worth taking seriously. The full-script rerun is real, and on a heavy app with many concurrent users it can stutter. But the critique is also the case for the skill: the features that fix it are exactly what newcomers skip. @st.cache_data stops the refetch on every interaction. @st.cache_resource keeps one DB engine instead of a new one per rerun. st.fragment reruns one panel instead of the page. Use cases 2, 3, and 9 each target one of those. Streamlit is still a prototyping-and-internal-tools framework, not a high-traffic public web app — pick it where that fits, and the rerun model stops being a problem.
One comparison worth naming up front: there is no dedicated Streamlit MCP server in our catalog, and you wouldn’t want one for authoring — the skill writes the app code locally, which is the entire job. MCP earns its place at the data layer instead. Pair the skill with Postgres, DuckDB, or Supabase so your app reads live rows rather than a static CSV. The skill builds the dashboard; the MCP server feeds it.
Real apps shipped with Streamlit
Concrete examples from public projects. Most don’t use the Claude skill specifically — they’re here to show what production-grade Streamlit looks like, so you have a target shape in mind when you write the prompt.
- Snowflake engineering — an internal ecosystem of 500+ Streamlit apps across 70+ teams (financial reporting, security, analytics)
- Streamlit team — streamlit/llm-examples, the canonical OpenAI / LangChain / file-Q&A chat starter apps
- LangChain team — langchain-ai/streamlit-agent, reference LangChain agents deployed as Streamlit chat apps
- Yuichiro Tachibana (whitphx) — streamlit-webrtc, real-time video/audio processing (CV demos) on Streamlit
- Streamlit team — demo-self-driving, the self-driving-car image browser with live ML inference
- Johannes Rieke (Streamlit) — best-of-streamlit, a ranked gallery of ~100 community Streamlit apps
Gotchas (the four that bite)
Sourced from the Streamlit architecture docs and the patterns the skill’s reference files call out. Each is a direct consequence of the script-rerun model.
The whole script reruns on every interaction
Click a button, move a slider — Streamlit re-executes app.py top to bottom. Anything not cached or stored in st.session_state recomputes. This is the root cause of most 'why is my app slow' complaints; the fix is @st.cache_data on the heavy calls, not restructuring the app.
cache_data vs cache_resource is not interchangeable
@st.cache_data returns a copy of the value (good for DataFrames and API results). @st.cache_resource returns the same object every time (right for DB connections and ML models). Use cache_data on a connection and every session gets its own — you'll exhaust the pool. The skill picks the correct one per call.
Widget state resets unless you key it
Values vanish across reruns unless held in st.session_state. Initialize state at the top with setdefault(), and give widgets a stable key= when you need to read or set them programmatically. The wizard in use case 3 lives or dies on this.
Forms batch input — and you have to opt in
Without st.form, every keystroke in a text_input triggers a full rerun. Wrap related inputs in a st.form so nothing reruns until the user clicks st.form_submit_button. Forget it on a multi-field form and the page flickers on every character.
Pairs well with
Curated to match the cookbook’s actual integrations: the data-app skills (interactive-dashboard-builder, plotly, data-visualization, pandas-data-analysis) plus the database MCP servers the data-backed use cases (1, 2, 7, 9) lean on.
Related skills
Related MCP servers
Two posts that compose well with this cookbook: What are Claude Code skills? covers the underlying mechanism, and the DuckDB skill cookbook covers the query engine that backs a fast Streamlit dashboard — load with DuckDB, display with Streamlit, the natural data-app pair.
Frequently asked questions
What does the Streamlit skill add that the official docs don't?
It compresses 317 pages of Streamlit documentation into a triggered assistant, so Claude writes the right idiom without you searching the API reference. The win is the rerun model: it knows when to reach for @st.cache_data versus @st.cache_resource, when st.form prevents a stutter, and when st.fragment isolates a partial rerun. Those are the three things newcomers get wrong, and the skill bakes the correct pattern into the code it writes.
Do I need to know Streamlit before I use the Streamlit skill?
No. The skill writes the streamlit imports, widget calls, and layout for you. You need Python installed and `pip install streamlit`, then `streamlit run app.py` to see it. Reading what Claude wrote is a useful check — every cookbook entry above shows the exact API surface (st.navigation, st.data_editor, st.chat_input) so you can follow along.
Does the Streamlit skill handle the rerun model and caching correctly?
That's its strongest area. Streamlit reruns your whole script top to bottom on every interaction — the source of most performance complaints. The skill applies @st.cache_data for data pulls and transformations, @st.cache_resource for shared objects like DB engines and ML models, st.session_state to persist values across reruns, and @st.fragment to rerun only one panel. Use cases 2, 3, and 9 each target one of those mechanisms directly.
Can the skill build multi-page Streamlit apps and editable tables?
Yes. Use case 1 scaffolds a multi-page app with st.Page and st.navigation and a shared cached loader. Use case 7 builds an editable grid with st.data_editor, including column_config formatting and a diff-and-write-back to the database on Save. Both are first-class Streamlit features the skill's reference files cover in full.
Is there a Streamlit MCP server I should use instead of the skill?
There isn't a dedicated Streamlit MCP server in our catalog, and you wouldn't want one for authoring — the skill writes the app code locally, which is the whole job. Where MCP helps is the data layer: pair the Streamlit skill with a database server like Postgres, DuckDB, or Supabase so your app reads live rows instead of a static CSV. The skill builds the UI; the MCP server feeds it data.
How do I add login to a Streamlit app the skill builds?
Use case 8 covers it. Streamlit ships native OpenID Connect auth: st.login() starts the flow, st.user.is_logged_in gates the page, and st.logout() ends the session. Credentials live in .streamlit/secrets.toml under an [auth] block — never in the Python. The skill writes both the gate and the secrets.toml stub with placeholders, so no real keys touch the code.
How do I deploy the Streamlit app once the skill writes it?
Two common paths, both in use case 10. Streamlit Community Cloud connects to a GitHub repo and reads requirements.txt; secrets move to its in-dashboard secrets UI. For your own infra, the skill writes a Dockerfile that runs `streamlit run app.py --server.address=0.0.0.0` and a HEALTHCHECK against /_stcore/health. Streamlit-in-Snowflake is the third option when the data already lives in Snowflake.
Sources
Primary
- sverzijl/streamlit skill — SKILL.md + references (api, concepts, tutorials, deployment)
- Streamlit official documentation
- Streamlit caching concepts (@st.cache_data vs @st.cache_resource)
- Streamlit session state & the rerun model
- Streamlit fragments (st.fragment partial reruns)
Community
- everling — Hacker News
- peddling-brink — Hacker News
- Mx-Mercedes — Reddit
- Asleep-Fisherman3 — Reddit
- rg111 — Hacker News
- Adrien Treuille (Streamlit co-founder) — Blog / transcript
Critical and contrarian
Internal