Python-native web components for starHTML/Datastar
Project description
starelements
Custom web components, defined in Python, powered by Datastar signals.
You decorate a Python function, and it becomes a custom element. Each instance gets its own scoped signals — no JavaScript class boilerplate, no state collision between multiple instances on the same page.
Why?
starelements gives you encapsulated custom elements where signals are scoped per instance and JS dependencies are declared with the component, not in your app headers. Define it once, app.register() it, and use it like any HTML element.
Features
- Decorator = component — one
@element("tag-name")and your function is a custom element. No class inheritance, noconnectedCallback. - Scoped signals — each component instance gets its own signal namespace. Two
<my-counter>on the same page won't step on each other. - ESM imports built in — pull in third-party JS via
imports={"chart": "https://esm.sh/chart.js@4"}. No bundler config needed. - Light DOM by default — your component's markup lives in the real DOM, so
<form>submission, CSS selectors, and accessibility tools all just work. Shadow DOM is opt-in. - Skeleton loading — set
height="400px", skeleton=Trueand users see a shimmer placeholder until the component initializes. Prevents layout shift.
Installation
Requires Python 3.12+ and StarHTML.
pip install starelements
Quick Start
A complete counter app — two instances with different initial values, each tracking its own state:
from starhtml import Div, Button, Span, star_app, serve
from starelements import element, Local
@element("my-counter")
def Counter():
return Div(
(count := Local("count", 0)),
(step := Local("step", 1)),
Button("-", data_on_click=count.set(count - step)),
Span(data_text=count),
Button("+", data_on_click=count.set(count + step)),
)
app, rt = star_app()
app.register(Counter)
@rt("/")
def home():
return Div(Counter(count=10, step=5), Counter(count=0))
if __name__ == "__main__":
serve()
Attributes you pass (count=10, step=5) become signal values inside that instance.
Local objects are signal references — count + step isn't evaluated in Python. It builds a JS expression, so count.set(count + step) produces $$count = ($$count + $$step) for the browser.
Examples
Skeleton loading
The skeleton option shows a shimmer placeholder while the component initializes, preventing layout shift:
@element("heavy-chart", height="400px", skeleton=True,
imports={"chart": "https://esm.sh/chart.js@4"})
def HeavyChart():
return Div(
Script('''
new chart.Chart(refs('canvas'), {type: 'bar', data: {...}});
'''),
Canvas(data_ref="canvas", style="width:100%;height:100%;"),
)
Setup and cleanup with Script()
Script() inside your render tree runs once when the component connects. Use onCleanup() to tear down resources when the element is removed:
@element("video-player")
def VideoPlayer():
return Div(
(playing := Local("playing", False)),
Video(data_ref="video", src="/video.mp4"),
Button("Play/Pause", data_on_click="$$playing = !$$playing"),
Script('''
const video = refs('video');
effect(() => $$playing ? video.play() : video.pause());
onCleanup(() => video.pause());
'''),
)
Inside Script(), imported modules are available by alias, signals are accessible as $$name, and refs('name') returns elements marked with data_ref.
For more complex examples, see:
examples/counter.py— counter with step controlsexamples/waveform_editor.py— audio waveform editor using Peaks.jsexamples/codemirror/editor.py— CodeMirror 6 with theme/language switching and complex import maps
API Reference
@element decorator
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Custom element tag (must contain a hyphen, e.g. my-counter) |
shadow |
bool |
False |
Use Shadow DOM instead of Light DOM |
form_associated |
bool |
False |
Reserved for future form association support |
height |
str | None |
None |
Shorthand for min-height; skeleton defaults to True when set |
width |
str |
"100%" |
Width dimension |
dimensions |
dict | None |
None |
Full dimension dict (overrides height/width) |
skeleton |
bool | None |
None |
Show shimmer placeholder while loading |
imports |
dict | None |
None |
ESM imports — {alias: specifier} |
import_map |
dict | None |
None |
Additional import map entries |
scripts |
dict | None |
None |
UMD scripts — {globalName: url} |
events |
list | None |
None |
Custom events the component emits |
Registration
app.register() mounts static file routes and adds the component's CSS, import map, JS runtime, and templates to the app-wide headers (included on every page):
app, rt = star_app()
app.register(Counter) # single component
app.register(Counter, DatePicker) # multiple at once
CLI
starelements includes a CLI for bundling npm packages into ESM bundles using esbuild:
starelements bundle # bundles packages listed in pyproject.toml [tool.starelements]
Configure packages in your pyproject.toml:
[tool.starelements]
bundle = ["chart.js@4", "@codemirror/state@6.4.1"]
Development
uv sync --all-extras # install dev + test dependencies
uv run scripts/build.py # build JS runtime from TypeScript
uv run ruff check src/ tests/ # lint
uv run pytest tests/ -v # run tests
The TypeScript runtime source lives in typescript/. The build script compiles it to src/starelements/static/starelements.min.js.
License
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file starelements-0.1.1.tar.gz.
File metadata
- Download URL: starelements-0.1.1.tar.gz
- Upload date:
- Size: 110.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ec025ff6ed1ee033e145f6b1b17ea660cf3b5e52f12d608837fa092f6d39a6bc
|
|
| MD5 |
5635c1e4411ec7aa913f346b0f4b3c2e
|
|
| BLAKE2b-256 |
9190b5eeb78865a432ecce4f94ec4879678880de9f81894c957579add7554af4
|
Provenance
The following attestation bundles were made for starelements-0.1.1.tar.gz:
Publisher:
release.yml on banditburai/starelements
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
starelements-0.1.1.tar.gz -
Subject digest:
ec025ff6ed1ee033e145f6b1b17ea660cf3b5e52f12d608837fa092f6d39a6bc - Sigstore transparency entry: 942496951
- Sigstore integration time:
-
Permalink:
banditburai/starelements@32e78a9ea416f6e5dcfa87506d4b76042aa184ad -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/banditburai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@32e78a9ea416f6e5dcfa87506d4b76042aa184ad -
Trigger Event:
push
-
Statement type:
File details
Details for the file starelements-0.1.1-py3-none-any.whl.
File metadata
- Download URL: starelements-0.1.1-py3-none-any.whl
- Upload date:
- Size: 26.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e7c184bad37e5a5421460fae17f47b3b1cc79d7a1c89bdc43bde8596962029a4
|
|
| MD5 |
3250253b6987af1198acfbbeace61529
|
|
| BLAKE2b-256 |
611b0d4e8d20b49f7e1509b4078a6651b593e454f5289b487612fad7c21b74a3
|
Provenance
The following attestation bundles were made for starelements-0.1.1-py3-none-any.whl:
Publisher:
release.yml on banditburai/starelements
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
starelements-0.1.1-py3-none-any.whl -
Subject digest:
e7c184bad37e5a5421460fae17f47b3b1cc79d7a1c89bdc43bde8596962029a4 - Sigstore transparency entry: 942496958
- Sigstore integration time:
-
Permalink:
banditburai/starelements@32e78a9ea416f6e5dcfa87506d4b76042aa184ad -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/banditburai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@32e78a9ea416f6e5dcfa87506d4b76042aa184ad -
Trigger Event:
push
-
Statement type: