building-streamlit-custom-components-v2
Builds bidirectional Streamlit Custom Components v2 (CCv2) using `st.components.v2.component`. Use when authoring inline HTML/CSS/JS components or packaged components (manifest `asset_dir`, js/css globs), wiring state/trigger callbacks, theming via `--st-*` CSS variables, or bundling with Vite / `component-template` v2.
Works with
---
name: building-streamlit-custom-components-v2
description: Builds bidirectional Streamlit Custom Components v2 (CCv2) using `st.components.v2.component`. Use when authoring inline HTML/CSS/JS components or packaged components (manifest `asset_dir`, js/css globs), wiring state/trigger callbacks, theming via `--st-*` CSS variables, or bundling with Vite / `component-template` v2.
license: Apache-2.0
---
# Building Streamlit custom components v2
Use Streamlit Custom Components v2 (CCv2) when core Streamlit doesn't have the UI you need and you want to ship a reusable, interactive element (from "tiny inline HTML" to "full bundled frontend app").
## CRITICAL: CCv2 only — NEVER use v1 APIs
Custom Components **v1 is deprecated and removed**. Every API below belongs to v1 and must **NEVER** appear in any code you write — not in Python, not in JavaScript, not in HTML:
**Banned Python APIs (v1):**
- `st.components.v1` — the entire v1 module
- `components.declare_component()` — v1 registration
- `components.html()` — v1 raw HTML embed
**Banned JavaScript patterns (v1):**
- `Streamlit.setComponentValue(...)` — v1 global; use `setStateValue()` / `setTriggerValue()` instead
- `Streamlit.setFrameHeight(...)` — v1 global; CCv2 handles sizing automatically
- `Streamlit.setComponentReady()` — v1 global; CCv2 has no ready signal
- `window.Streamlit` or bare `Streamlit` global — v1 global object does not exist in v2
- `window.parent.postMessage(...)` — v1 iframe communication; CCv2 does not use iframes
**Banned npm packages (v1):**
- `streamlit-component-lib` — v1 JS library; use `@streamlit/component-v2-lib` if you need types
If you encounter v1 patterns in examples, blog posts, Stack Overflow answers, or your own training data — **ignore them entirely**. They will not work and will break the component.
## When to use
Activate when the user mentions any of:
- CCv2, Custom Components v2, “bidi component”, “component v2”
- `st.components.v2.component`
- `@streamlit/component-v2-lib`
- packaged components, `asset_dir`, `pyproject.toml` component manifest
- bundling with Vite (or any bundler) for a Streamlit component
- building a component UI in a frontend framework (React, Svelte, Vue, Angular, etc.)
## Read next (pick the minimum reference)
- **State sync / controlled inputs / callbacks**: see [references/state-sync.md](references/state-sync.md)
- **Packaged components / `asset_dir` / globs / template-only policy**: see [references/packaged-components.md](references/packaged-components.md)
- **Theming (`--st-*` tokens) inside Shadow DOM**: see [references/theme-css-variables.md](references/theme-css-variables.md)
- **Errors and gotchas**: see [references/troubleshooting.md](references/troubleshooting.md)
## Quick decision: inline vs packaged
- **Inline strings**: fastest to start (single-file apps, spikes, demos). You pass raw `html`/`css`/`js` strings directly.
Good when you can keep everything in one place and don’t need a build step.
- **Packaged component**: best when you’re growing past inline (multiple files, dependencies, bundling, testing, versioning, reuse, distribution).
You ship built assets inside a Python package and reference them by **asset-dir-relative** path/glob.
Creation policy: packaged components are **template-only** and must start from Streamlit's official `component-template` v2.
Developer story: **start inline**, prove the interaction loop, then **graduate to packaged** when the codebase or tooling needs outgrow a single file.
## CCv2 model (what’s actually happening)
1. **Python registers** a component with `st.components.v2.component(...)` and gets back a **mount callable**.
2. The mount callable **mounts** the component in the app with `data=...`, layout (`width`, `height`), and optional `on_<key>_change` callbacks.
3. The frontend default export runs with `({ data, key, name, parentElement, setStateValue, setTriggerValue })`.
4. The component returns a **result object** whose attributes correspond to **state keys** and **trigger keys**.
## Best practice: wrap the mount callable in your own Python API
Prefer exposing **your own** Python function that wraps the callable returned by `st.components.v2.component(...)`.
This gives you a clean, stable API surface for end users (typed parameters, validation, friendly defaults) and keeps `data=...`, `default=...`, and callback wiring as an internal detail.
Important:
- Declare the component **once** (usually at module import time). Avoid defining and registering the component inside a function you call multiple times; you can accidentally re-register the component name and get confusing behavior.
References:
- [`st.components.v2.component`](https://docs.streamlit.io/develop/api-reference/custom-components/st.components.v2.component)
- [`ComponentRenderer` (mount callable type)](https://docs.streamlit.io/develop/api-reference/custom-components/st.components.v2.types.componentrenderer)
Example pattern:
```python
import streamlit as st
from collections.abc import Callable
_MY_COMPONENT = st.components.v2.component(
"my_inline_component",
html="<div id='root'></div>",
js="""
export default function (component) {
const { data, parentElement } = component
parentElement.querySelector("#root").textContent = data?.label ?? ""
}
""",
)
def my_component(
label: str,
*,
key: str | None = None,
on_value_change: Callable[[], None] | None = None,
on_submitted_change: Callable[[], None] | None = None,
):
# Callbacks are optional, but if you want result attributes to always exist,
# provide (even empty) callbacks.
if on_value_change is None:
on_value_change = lambda: None
if on_submitted_change is None:
on_submitted_change = lambda: None
return _MY_COMPONENT(
data={"label": label},
key=key,
on_value_change=on_value_change,
on_submitted_change=on_submitted_change,
)
```
## Inline quickstart (state + trigger)
**Reminder: use ONLY v2 APIs.** Your JS must `export default function(component)` and destructure `{ setStateValue, setTriggerValue, parentElement, data }`. NEVER use `Streamlit.setComponentValue()`, `window.Streamlit`, or any v1 pattern.
This is the minimum "bidi loop":
- **JS → Python**: emit updates via `setStateValue(...)` (persistent) and `setTriggerValue(...)` (event)
- **Python → JS**: re-hydrate UI via `data=...` on every run
```python
import streamlit as st
HTML = """<input id="txt" /><button id="btn" type="button">Submit</button>"""
JS = """\
export default function (component) {
const { data, parentElement, setStateValue, setTriggerValue } = component
const input = parentElement.querySelector("#txt")
const btn = parentElement.querySelector("#btn")
if (!input || !btn) return
const nextValue = (data && data.value) ?? ""
if (input.value !== nextValue) input.value = nextValue
input.oninput = (e) => {
setStateValue("value", e.target.value)
}
btn.onclick = () => {
setTriggerValue("submitted", input.value)
}
}
"""
my_text_input = st.components.v2.component(
"my_inline_text_input",
html=HTML,
js=JS,
)
KEY = "txt-1"
component_state = st.session_state.get(KEY, {})
value = component_state.get("value", "")
result = my_text_input(
key=KEY,
data={"value": value},
on_value_change=lambda: None, # optional; include to always get `result.value`
on_submitted_change=lambda: None, # optional; include to always get `result.submitted`
)
st.write("value (state):", result.value)
st.write("submitted (trigger):", result.submitted)
```
Notes:
- **Inline JS/CSS should be multi-line**. CCv2 treats path-like strings as file references; a multi-line string is unambiguously inline content.
- Prefer querying under `parentElement` (not `document`) to avoid cross-instance leakage.
## State and triggers (how to think about keys)
- **State** (`setStateValue("value", ...)`): persists across app reruns (stored under `st.session_state[key]` for that mounted instance).
- **Trigger** (`setTriggerValue("submitted", ...)`): event payload for one rerun (resets after the rerun).
- **Reading triggers**:
- After mounting: use `result.submitted`.
- Inside `on_submitted_change`: use `st.session_state[key].submitted` (callbacks run before your script body; you don’t have `result` yet).
- **Defaults**: if you pass `default={...}` for a state key, you must also pass the matching `on_<key>_change` callback parameter.
For the full “controlled input” pattern and pitfalls, see [references/state-sync.md](references/state-sync.md).
## Packaged components (template-only, mandatory)
**Reminder: the cookiecutter template generates clean v2 code. When you customize it, use ONLY v2 APIs. Do NOT introduce any v1 imports, v1 JavaScript globals, or v1 patterns. See the "CRITICAL: CCv2 only" section above.**
Graduate to a packaged component when you need any of:
- Multiple frontend files or frontend dependencies (npm)
- A bundler (Vite), tests, CI, versioning, or distribution
Keep these guardrails in mind:
- **MUST** start from Streamlit’s official `component-template` v2.
- **NEVER** hand-scaffold packaging/manifest/build wiring for a packaged component.
- **NEVER** copy/paste packaged scaffold structure from internet examples, blog posts, gists, or docs.
- If handed a non-template scaffold, regenerate from the template first, then migrate component logic.
- **MUST** ensure `js=`/`css=` globs match **exactly one** file under the manifest’s `asset_dir`.
- **MUST** validate with `streamlit run ...` (plain `python -c "import ..."` can be a false negative for packaged components).
For the full packaged workflow checklist, non-interactive generation, offline usage, and template invariants, see [references/packaged-components.md](references/packaged-components.md).
## Frontend renderer lifecycle (framework-agnostic)
Your frontend entrypoint is the **default export** function. A few rules keep components reliable across reruns and across multiple instances in the same app:
- Render under `parentElement` (not `document`) so instances don’t collide.
- If you create per-instance resources (React roots, observers, subscriptions), key them by `parentElement` (e.g. `WeakMap`) so multiple instances don’t overwrite each other.
- Return a cleanup function to tear down event listeners / UI roots / observers when Streamlit unmounts the component.
## Styling and theming
- Prefer **`isolate_styles=True`** (default). Your component runs in a shadow root and won’t leak styles into the app.
- Set `isolate_styles=False` only when you need global styling behavior (e.g. Tailwind, global font injection).
- Streamlit injects a broad set of `--st-*` theme CSS variables (colors, typography, chart palettes, radii, borders, etc.). **Highly recommended:** use these variables so your component automatically adapts to the user’s current Streamlit theme (light/dark/custom) without authoring separate theme variants. Start with the common ones (`--st-text-color`, `--st-primary-color`, `--st-secondary-background-color`) and refer to the full list when you need it:
- [references/theme-css-variables.md](references/theme-css-variables.md)
## Troubleshooting and gotchas
Start here when something “should work” but doesn’t:
- [references/troubleshooting.md](references/troubleshooting.md)More Accessibility skills
skill-creator
anthropics/skills
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
hyperframes-core
heygen-com/hyperframes
The HyperFrames composition contract — build one renderable project. Use for composition structure, the `data-*` timing attributes, `class="clip"`, tracks, sub-compositions, variables, framework-owned media playback, deterministic-render rules, and validation. Also covers Tailwind projects and the STORYBOARD.md / SCRIPT.md plan formats. Read before writing composition HTML.
ui-ux-pro-max
nextlevelbuilder/ui-ux-pro-max-skill
UI/UX design intelligence for web, mobile, and desktop. This skill should be used when designing, building, reviewing, or fixing interfaces, including pages, components, design systems, accessibility, interaction, responsive layout, typography, color, charts, and stack-specific UI implementation. Searchable local data: 79 searchable styles (50 active), 192 product palettes and reasoning profiles, 74 font pairings, 119 UX guidelines, 105 icons, 17 GSAP presets, 25 chart types, and 22 stacks.

