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.
Best use case
building-streamlit-custom-components-v2 is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
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.
Teams using building-streamlit-custom-components-v2 should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/building-streamlit-custom-components-v2/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How building-streamlit-custom-components-v2 Compares
| Feature / Agent | building-streamlit-custom-components-v2 | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
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.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# 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)Related Skills
claude-flow-hook-customizing
Use this skill when creating, optimizing, or maintaining claude hooks.
building-chatgpt-apps
Guides creation of ChatGPT Apps with interactive widgets using the Apps SDK and MCP servers. Use when building ChatGPT custom apps with visual UI components, embedded widgets, or rich interactive experiences. Covers widget architecture, MCP server setup with FastMCP, response metadata, and Developer Mode configuration. NOT when building standard MCP servers without widgets (use building-mcp-servers skill instead).
analyzing-customers
Analyzes customer behavior, needs, pain points, and sentiment through review mining, social listening, buyer persona development, and jobs-to-be-done framework. Use when the user requests customer analysis, voice of customer research, buyer personas, pain point analysis, or wants to understand customer needs and motivations.
ameba-custom-rules
Use when creating custom Ameba rules for Crystal code analysis including rule development, AST traversal, issue reporting, and rule testing.
ui-components
UI component library patterns for shadcn/ui and Radix Primitives. Use when building accessible component libraries, customizing shadcn components, using Radix unstyled primitives, or creating design system foundations.
swiftui-components
SwiftUI component expert for building reusable views, custom modifiers, view compositions, and Liquid Glass integration. Use when creating new SwiftUI views, refactoring UI code, applying design tokens, or building production-ready component libraries for macOS Tahoe (26) and iOS 26.
hig-components-controls
Apple HIG guidance for selection and input controls including pickers, toggles, sliders, steppers, segmented controls, combo boxes, text fields, text views, labels, token fields, virtual...
core-components
Core component library and design system patterns. Use when building UI, using design tokens, or working with the component library.
Building Agent Skills
Assists in creating Agent Skills of varying complexity levels (simple, moderate, complex). Use when the user wants to create, design, or build a new Agent Skill, or when they need guidance on skill architecture, workflow design, schema validation, or template structure.
building-a-work-plan
Use when orchestrating the creation of a work plan from a design document. Manages branch setup, codebase investigation, milestone planning, and execution handoff.
budget-ui-components
Componentes de UI para orçamentos seguindo o padrão de components do Easy Budget.
ai-native-product-building
Rapidly build, prototype, and deploy full-stack software using AI "text-to-app" tools. Use this when you need to create a greenfield application, build a high-fidelity working prototype for user testing, or bypass traditional engineering bottlenecks for internal tools.