- Packaged assets and manifests (
asset_dir, component key) - Renaming / placeholder drift
- Inline strings vs file-backed assets (path heuristic)
- Globs (0 matches or multiple matches)
- Defaults, callbacks, and missing result attributes
- Keys (Python
key=vs frontendkey) - Shadow DOM /
isolate_stylessurprises - Frontend build (Vite) gotchas
- DOM clobbering (overwriting injected HTML/CSS)
You passed a path-like js=/css= string (like index-*.js or assets/index-*.js) but Streamlit can’t find an asset_dir for this component key.
Fix:
- If you want inline JS/CSS, pass a multi-line string with the actual code (not a path).
- If you want packaged assets, ensure:
- Your wheel includes a
pyproject.tomlwith[[tool.streamlit.component.components]] ... asset_dir = ... - You call
st.components.v2.component("<project>.<component>", js="...", css="...")with the matching fully-qualified key.
- Your wheel includes a
Important context:
- This error is expected if you test packaged wrappers via plain Python import in some environments.
- Prefer
streamlit run ...for packaged verification because manifest discovery is part of Streamlit runtime initialization.
CCv2 uses a heuristic: strings that “look like” paths are treated as file references. A multi-line string is always treated as inline content.
Fix:
- Prefer triple-quoted multi-line strings for inline
html/css/js. - Avoid single-line minified JS/CSS in
js=/css=; add a newline if you must.
Globs must match exactly one file under asset_dir.
Fix:
- Clean the build output directory before rebuilding.
- Make your bundler output a predictable
index-<hash>.js/index-<hash>.css(orassets/index-<hash>...if you emit into anassets/subdir). - If you started from Streamlit’s
component-template, runnpm run cleanfrom yourfrontend/directory to clear thebuild/output soindex-*.jsmatches exactly one file.
Symptoms:
- Old names like
streamlit-component-x/streamlit_component_xremain in paths or metadata. - Imports, manifest component keys, and registration keys no longer align.
Fix:
- Rename/update all related surfaces together:
- root
pyproject.tomlproject name - import package folder and
MANIFEST.inpaths [tool.setuptools.packages.find]and[tool.setuptools.package-data]- in-package manifest (
<import_name>/pyproject.toml) - wrapper registration key (
"<project.name>.<component.name>") - README/example imports and install commands
- root
- Rebuild frontend and reinstall editable package after rename.
Defaults only apply to state keys, and Streamlit expects those keys to be declared via on_<key>_change callback parameters at mount time.
Fix:
- If you pass
default={"value": ...}, also passon_value_change=lambda: None. - For triggers, don’t expect defaults; triggers are transient and default to
None.
- Python
key=is the user-visible Streamlit element key. - The frontend also receives a
keystring that is generated by Streamlit and is not the same as the Pythonkeyunless you explicitly pass the user key throughdata.
Fix:
- If your frontend needs a stable identifier, pass it in
data={"user_key": key, ...}.
With isolate_styles=True (default):
- Your component is mounted in a shadow root.
parentElementis aShadowRoot.- Global CSS (like Tailwind injected into the document) won’t automatically style your component.
Fix:
- Keep
isolate_styles=Truefor safety and use CSS variables and component-local styles. - Use
isolate_styles=Falseonly when you intentionally need global styling behavior.
If you deviate from the template’s Vite config (or you’re wiring Vite into an existing repo), these are the common footguns:
- Missing
base: "./": relative asset URLs can break when served from Streamlit’s component URL path. - Stale build artifacts: Vite outputs hashed filenames; if you keep old builds around,
index-*.jscan match multiple files. Clean the build dir before rebuilding.
If you directly set parentElement.innerHTML = ..., you can overwrite the HTML/CSS that Streamlit injected from your html=/css= arguments.
Fix:
- Prefer
querySelector+ modifying children. - If you need dynamic HTML, create a new child element and set that element’s
innerHTML.