component-template: the starting point for a Streamlit custom component
Templates and example code for creating Streamlit Components
At a glance
- What is it?
- The Streamlit component-template repository holds the official templates for building custom Streamlit components, in both the current v2 API and the legacy v1 one, with React and framework-free TypeScript variants and a cookiecutter that generates a project from either. It suits a Python developer who needs an interactive widget Streamlit does not ship, and the detail to plan around is that v2 components live in the page rather than in an iframe, which changes what the front end can do.
- Who is it for?
- Adopt this template if you need a Streamlit component that the built-in widgets do not cover, since it gives you a project skeleton, a working example and a regeneration script rather than leaving you to guess the file layout.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What this repository is
This repository holds the templates and example code for creating Streamlit components, and it is the official package template: generate a project from it, then customize.
That phrasing sets the expectation correctly. This is not a library you add to your application. It is a project skeleton that you run once to get a directory, which you then own. The generated project is a starting point with the wiring already in place, not something you keep up to date with upstream.
The reason the repository exists at all is that a Streamlit component is a two-part thing. There is a Python side, which is ordinary Streamlit code, and a front end, which is JavaScript or TypeScript running in the browser, and the two have to be declared to each other with matching names, matching argument lists and matching value types. Getting that handshake right is fiddly, and the template does it once for you.
The relationship to the framework itself is important. Components v2 is the way to build a new component, and it is covered by the documentation on the Streamlit site, covering registration, mounting, state versus trigger values, and packaged frontends. Components v1 is still supported for components that already use it.
That last clause is the version policy in one sentence. v1 exists and works, but the project is not asking anyone to begin there.
The licence is Apache 2.0, the primary language is Python, the default branch is master, and the last push was on 2026-10-16 with no GitHub releases, which is what you would expect for a repository whose output is a set of template directories rather than a distributable package.
How v2 components differ from v1
The differences between the two APIs are structural, and they are the reason the v2 templates look nothing like the v1 ones.
From the overview documentation, v2 components are part of the Streamlit page. They are not isolated in an iframe. You can pass multiple callbacks, state values persist, and trigger values fire once per event. Data exchange includes JSON, dataframes using Apache Arrow, and bytes. You can prototype with HTML, CSS and JavaScript from Python, or package a front end with TypeScript and Vite. And theme values are available as CSS custom properties.
Each of those is worth unpacking, because they accumulate into a different model.
Not being in an iframe is the big one. A v1 component runs in a sandboxed frame, which means it is isolated from the page but also unable to reach the page's styles, its theme, or its DOM. A v2 component is mounted into the page proper, so it inherits the theme, it can be styled by the app's own CSS, and it can use the browser's own layout rather than living inside a fixed-size box. That is the difference between a component that looks like it came from somewhere else and one that looks like part of the app.
The state versus trigger distinction is the second. A state value persists and a trigger value fires once per event. That split is what lets a component hold something like a slider position in state while a button click arrives as a trigger, which is exactly the distinction a v1 component could not express because it returned a single value and that value had to be either the state or the event.
The data formats matter for the Python side. JSON for ordinary payloads, dataframes over Apache Arrow for tabular data, and raw bytes for files. Arrow in particular is worth knowing about, because it is the efficient path for anything tabular and it is the reason a v2 component can accept a real dataframe rather than a list of dicts.
And the two front-end routes are worth contrasting. You can prototype with HTML, CSS and JavaScript from Python, which means you can ship a component with no build step at all, or you can package a front end with TypeScript and Vite, which is what you do when the component grows beyond a few lines.
Theme values as CSS custom properties is the last, and it is the feature that makes an embedded component look native: the component reads the theme the same way the rest of the page does.
The registration contract
The mechanics of a packaged v2 component are short, and the README states the contract precisely: a packaged component registers a fully qualified name, points `js=` at a file under `asset_dir`, and declares each state key in `default` with a matching `on_<key>_change` callback.
The Python side:
import streamlit as st
my_component = st.components.v2.component(
"your-package.your_component",
js="index-*.js",
html="<button>Click</button>",
)
value = my_component(
data={"name": "World"},
default={"num_clicks": 0},
on_num_clicks_change=lambda: None,
)Four things to notice in that. The name is fully qualified with a package prefix, which is what stops two components with the same function name from colliding in the registry. The `js=` argument is a glob pattern rather than a single filename, so the build output can be hashed and the pattern still resolves. The `html=` argument is the inline prototype form, where the front end is written directly in the Python file. And the `default` dictionary declares the state keys, each of which is paired with a matching callback named `on_<key>_change`.
That pairing is the whole of the state model. A key in `default` is a state value, and `on_num_clicks_change` is what fires when it changes. The lambda in the example does nothing, but in a real component it would be where you read the new value and act on it, typically by setting a Streamlit widget or triggering a rerun.
The front end mirrors it:
import { FrontendRenderer } from "@streamlit/component-v2-lib";
const MyComponent: FrontendRenderer = ({ parentElement, data, setStateValue }) => {
const button = parentElement.querySelector("button");
if (!button) return;
button.textContent = `Hello, ${data.name}!`;
button.onclick = () => setStateValue("num_clicks", 1);
};
export default MyComponent;The renderer receives `parentElement`, which is the DOM node the component has been given to fill, `data`, which is the dictionary passed from Python, and `setStateValue`, which is how the front end writes back into state. The component queries its own button out of the parent element, sets the text from the incoming data, and on a click calls `setStateValue("num_clicks", 1)`.
That call is the trigger firing. It sets the state value, and the Python side's `on_num_clicks_change` callback is what runs in response. The two halves meet on the key name, which is why the naming convention matters and why the keys in `default` and the strings passed to `setStateValue` have to match exactly.
The `if (!button) return;` guard is worth keeping in mind as a habit. The renderer is called with a parent element, and defensive checks in that position are cheap insurance against a render before the element exists.
Generating a project
The quickstart is a single command, and the requirements are listed first: install uv, Node.js 24, and npm.
For a v2 component:
uvx --from cookiecutter cookiecutter gh:streamlit/component-template --directory cookiecutter/v2And for a legacy v1 component:
uvx --from cookiecutter cookiecutter gh:streamlit/component-template --directory cookiecutter/v1The `gh:` prefix in the argument is GitHub shorthand, so the generator pulls the repository directly from GitHub without cloning it yourself. The `--directory` flag selects which subdirectory of the cookiecutter configuration to use, which is how one repository carries templates for two different APIs.
Using `uvx --from cookiecutter` means the cookiecutter tool itself is fetched and run in a temporary environment, so nothing is installed globally and there is no version conflict with anything else on the machine. For a one-shot project generator that is the right arrangement.
After the prompts, the instruction is to follow the README in the generated project. The generated project has its own README, which means the documentation is per-project rather than shared, and it can be edited along with the project without affecting anyone else.
Sample apps install with uv and build the front end with Vite. That is the same pair of tools the v2 documentation mentions for prototyping with TypeScript and Vite, and it is worth noting that the front end build is a separate step from the Python install. A developer changing the TypeScript has to rebuild the bundle before the Python side sees the change, which is the usual cost of a packaged component as opposed to the inline HTML form.
The pre-generated templates directory is the part of the repository that is most useful for reading rather than running. The `templates/` directory is the cookiecutter output, so you can read a complete project without generating one: v2 at `templates/v2/template/` and `templates/v2/template-reactless/`, and v1 at `templates/v1/template/` and `templates/v1/template-reactless/`.
The reactless variants are the framework-free TypeScript ones, and the difference between the two is the one that matters most when choosing a template. The React template gives you a component written as a React component, with the React toolchain and its build already configured. The reactless one gives you the same contract in plain TypeScript, with no framework in between, which is what the FrontendRenderer example in the README is written as. If your component is genuinely a leaf with one widget in it, the reactless version is less machinery.
Community templates and the regeneration script
Four community-provided templates are listed, and the note about them is important: they are maintained outside this repository, and issues should be filed against their repositories.
There is a Svelte template by 93degree, a Vue and Vite template by gabrieltempass, a Vue template by andfanilo, and a React hooks template by whitphx. Each is credited to the person who maintains it.
The list exists because the two official templates cover React and framework-free TypeScript, and those are not the only frontend choices anyone makes. If you want to write your component in Svelte, the official template is not the right starting point and one of these is.
The maintenance arrangement deserves a note. Community templates are not vendored into this repository, so they can move at their own pace and follow their own framework's conventions. The cost is that there is no single place where all the templates are tested together, which is why the instruction is to file issues against the right repository rather than here.
The contributing section describes a small piece of tooling that is easy to miss. `./dev.py` refreshes the rendered templates and checks that they still match the cookiecutter sources.
That is a two-way check and it is the kind of thing a repository with generated content needs. The `templates/` directory is generated output, and generated output drifts unless something checks it. Here the script regenerates it and then verifies that the result still matches what is committed, so a change to the cookiecutter source that somebody forgets to propagate shows up as a failing check rather than as a template that no longer matches its source.
The commit history on GitHub for this repository would show the pattern, since the contributing guidance in CONTRIBUTING.md is where the details would be.
For more information, the README points at the custom components overview in the documentation, the Streamlit forums with a custom components tag, and the Streamlit components gallery, which is the place to look before writing one, since an existing component may already do what you need.
Editorial conclusion
Adopt this template if you need a Streamlit component that the built-in widgets do not cover, since it gives you a project skeleton, a working example and a regeneration script rather than leaving you to guess the file layout. Do not start a new component on the v1 API, since the README says new components should use v2 and v1 is only for components that already use it, and check first whether one of the community-provided templates already covers your framework, since there are maintained Svelte, Vue and React hooks variants outside this repository.
Frequently asked questions
What is the Streamlit component-template repository?
It holds the official templates and example code for creating Streamlit components, in the current Components v2 API and the legacy v1 one. It is a project skeleton rather than a library: you generate a project from it with cookiecutter and then customize it. The licence is Apache-2.0.
What is the difference between Streamlit components v1 and v2?
v2 components are part of the Streamlit page rather than isolated in an iframe, support multiple callbacks with state values that persist and trigger values that fire once per event, exchange JSON, Apache Arrow dataframes and bytes, and can be prototyped with HTML, CSS and JavaScript from Python or packaged with TypeScript and Vite. v1 components run in an iframe and the API is mainly one-way.
How do I generate a new Streamlit component project?
Install uv, Node.js 24 and npm, then run `uvx --from cookiecutter cookiecutter gh:streamlit/component-template --directory cookiecutter/v2` for a v2 component, or the same command with cookiecutter/v1 for a legacy one. Follow the prompts, then the README in the generated project, and build the front end with Vite.
How does a packaged v2 component declare its state values?
It registers a fully qualified name, points the js argument at a file under asset_dir, and declares each state key in the default dictionary with a matching on_<key>_change callback. The front end writes back with setStateValue using the same key name, which is what triggers the Python callback.
Are there Streamlit component templates for other frontend frameworks?
Yes, and they are maintained outside this repository, with issues filed against their own repositories. There are community templates for Svelte, for Vue with Vite, for Vue, and for React hooks, alongside the official React and framework-free TypeScript templates in the templates directory.
How is the Streamlit component-template repository maintained?
The templates directory is the cookiecutter output, and the contributing section says that ./dev.py refreshes the rendered templates and checks that they still match the cookiecutter sources, so a change to the source that is not propagated shows up as a failing check. There is also a CONTRIBUTING.md file with the details.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/streamlit-component-template)Was this useful?