Skip to main content

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, no connectedCallback.
  • 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=True and 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:

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

Apache 2.0

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

starelements-0.1.3.tar.gz (111.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

starelements-0.1.3-py3-none-any.whl (26.7 kB view details)

Uploaded Python 3

File details

Details for the file starelements-0.1.3.tar.gz.

File metadata

  • Download URL: starelements-0.1.3.tar.gz
  • Upload date:
  • Size: 111.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for starelements-0.1.3.tar.gz
Algorithm Hash digest
SHA256 b2ebe30c369411eab15c83af31bf03267d822bf4e9e1e70bbf4a6105e3ddd7db
MD5 e8a83dd1317a6739a8db5343e358c78c
BLAKE2b-256 3ccdd904e9b7172f5b66541e799565c64cd51137693a646a84e23344be55c447

See more details on using hashes here.

Provenance

The following attestation bundles were made for starelements-0.1.3.tar.gz:

Publisher: release.yml on banditburai/starelements

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file starelements-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: starelements-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 26.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for starelements-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 4608f8e26d9a4fa905642ae543b800bd14cd8c15de3e7586c1ca286b3d8dadde
MD5 116216dd66678405deb3206871641cac
BLAKE2b-256 5cd5e50225af088391dfd2b8e1ac01dea4cefb8adf1a27f7bcf228f4488d39b6

See more details on using hashes here.

Provenance

The following attestation bundles were made for starelements-0.1.3-py3-none-any.whl:

Publisher: release.yml on banditburai/starelements

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page