lib/streamlit/.agents/skills/developing-with-streamlit/references/ccv2-packaged-components.md
For packaged CCv2 components, agents MUST use Streamlit's official template as the starting point for every new component project.
Never hand-scaffold the package/manifest/build layout and never copy/paste a packaged component scaffold from blog posts, gists, docs, or other internet sources.
If a request starts from a non-template scaffold, stop and regenerate from the template first, then port logic into the generated project.
Follow your generated project's README. Only keep reading if you need to debug template wiring or customize behavior after template generation.
If the request is for a packaged CCv2 component:
__init__.py, JS/TS files, or example.py, use ONLY v2 APIs (st.components.v2.component, setStateValue, setTriggerValue, parentElement, data). Do NOT import st.components.v1, do NOT use declare_component(), do NOT use Streamlit.setComponentValue() or any v1 JavaScript globals. See the main SKILL.md "CRITICAL: CCv2 only" section for the full banned list.uv (recommended) + cookiecutter.Inline components are great for getting started quickly. Move to a packaged component when you hit any of these:
The official Streamlit component-template v2 supports both React + TypeScript (Vite) and Pure TypeScript (Vite) (no React). CCv2 also works with any frontend framework that compiles to JavaScript (Svelte, Vue, Angular, vanilla TS/JS, etc.).
The only requirement is that you produce JS/CSS assets into your component’s asset_dir, then register them from Python via html=..., js="...", and css="..." using asset-dir-relative paths/globs.
For end-to-end type safety while authoring the frontend, install @streamlit/component-v2-lib:
It provides TypeScript types like FrontendRenderer / FrontendRendererArgs so your export default renderer gets a typed data payload and typed state/trigger keys via generics.
This command is the required starting point for every packaged CCv2 component:
uvx --from cookiecutter cookiecutter gh:streamlit/component-template --directory cookiecutter/v2
If you run this non-interactively, pass explicit cookiecutter values (do not rely on defaults):
Template keys:
author_nameauthor_emailproject_namepackage_nameimport_namedescriptionopen_source_licenseframeworkRecommended non-interactive invocation:
This sample uses a hypothetical breadcrumb component name so the values are concrete and meaningful:
uvx --from cookiecutter cookiecutter gh:streamlit/component-template \
--directory cookiecutter/v2 \
--no-input \
author_name="Your Name" \
author_email="[email protected]" \
project_name="Streamlit Breadcrumbs" \
package_name="streamlit-breadcrumbs" \
import_name="streamlit_breadcrumbs" \
description="Packaged Streamlit CCv2 breadcrumb component" \
open_source_license="Apache-2.0" \
framework="React + Typescript"
Notes:
framework is "React + Typescript" or "Pure Typescript").Offline/airgapped:
uvx --from cookiecutter cookiecutter /path/to/component-template --directory cookiecutter/v2
From the generated project:
Activate the target project environment before Python/uv commands:
source /path/to/project/.venv/bin/activate
Build the frontend assets (from <import_name>/frontend):
npm i
npm run build
Editable install (project root containing pyproject.toml):
uv pip install -e . --force-reinstall
Run the example app with Streamlit:
streamlit run example.py
Why this order:
asset_dir contains the expected files before install/use.Use this when debugging or customizing after generation; it's designed to prevent the common "built assets exist but Streamlit can't load them" failure modes.
Packaged CCv2 checklist
- [ ] Generate project from `component-template` v2
- [ ] Confirm this is template-generated (not hand-scaffolded, not copied from internet snippets)
- [ ] Activate the target project environment before Python/uv commands
- [ ] Rename template defaults (`streamlit-component-x`, `streamlit_component_x`, etc.) if needed
- [ ] Build frontend assets into the manifest’s `asset_dir` (template: `frontend/build/`)
- [ ] Editable install the Python package (`uv pip install -e . --force-reinstall`)
- [ ] Verify `js=`/`css=` globs match exactly one file each under `asset_dir`
- [ ] Run via `streamlit run ...` and confirm the component renders/events work
- [ ] If something breaks: read `ccv2-troubleshooting.md`, fix, rebuild, re-verify glob uniqueness
asset_dir exists and contains the built assets.asset_dir:
js="index-*.js" and css="index-*.css"npm run clean) and rebuild.index.tsx)The React cookiecutter template has a three-file data flow. When you pass data={...} from Python, it flows through all three files before reaching the rendered component:
__init__.py — Python wrapper sends data={"name": name, "testId": test_id, ...} to the frontendindex.tsx — the bridge file that destructures data and passes props to the React component: const { name, testId } = data; → <MyComponent name={name} testId={testId} />MyComponent.tsx — receives props and renders the UI: <button data-testid={testId}>...</button>You MUST edit all three files when adding new data fields. The most common mistake is editing __init__.py and MyComponent.tsx but forgetting index.tsx. If you skip index.tsx, your new data fields are sent from Python but never reach the React component — props will be undefined.
data-testid propagation: When adding data-testid attributes for QA/testing, ensure the test ID value flows through the entire chain:
data={"testId": "my-button"}index.tsx extracts it: const { testId } = data; and passes it: <MyComponent testId={testId} />MyComponent.tsx renders it: <button data-testid={testId}>...</button>If any layer is missing, the data-testid won't appear on the rendered element.
You typically shouldn't need to touch these, but they explain most "why won't this load?" failures:
"<project.name>.<component.name>". Do NOT change the component name that the template generates — the template wires the fully-qualified name correctly across __init__.py, the inner pyproject.toml manifest, and the MANIFEST.in. Changing it in one place without updating all others will cause StreamlitAPIException: Component '...' must be declared in pyproject.toml with asset_dir to use file-backed js.<import_name>/pyproject.toml with asset_dir = "frontend/build". Do NOT rename [[tool.streamlit.component.components]] to anything else — this exact TOML key is required.js="index-*.js" (template default output) or js="assets/index-*.js" (if you configured an assets/ subdir).index-*.js matches multiple hashed builds, clean the build output (npm run clean) and rebuild.Template defaults like streamlit-component-x / streamlit_component_x should be replaced everywhere early.
Rename all of these together:
[project].name) in root pyproject.toml.streamlit_<real_name>).<import_name>/pyproject.toml).st.components.v2.component("<project.name>.<component.name>", ...)MANIFEST.in and [tool.setuptools.*] references.Keep the blast radius small:
js=/css= asset-dir-relative globs in the Python wrapper.base: "./" so relative URLs work when served from Streamlit’s component URLs.Validate packaged components with streamlit run ..., not plain python -c "import ..." checks.
asset_dir registration errors for otherwise-correct packaged components.