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.2.tar.gz (111.2 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.2-py3-none-any.whl (26.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: starelements-0.1.2.tar.gz
  • Upload date:
  • Size: 111.2 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.2.tar.gz
Algorithm Hash digest
SHA256 5fb2965f4c4db2a200579fd9795c577f2ab30549eab890f1f7283716f32bfba0
MD5 eda2636a6c827bf55a6082f71efcc198
BLAKE2b-256 67a0677eb6b86941dd4b34af684eb78fac6ec7f56187d0c80fd0ad9c586cdb5b

See more details on using hashes here.

Provenance

The following attestation bundles were made for starelements-0.1.2.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.2-py3-none-any.whl.

File metadata

  • Download URL: starelements-0.1.2-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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 e12289df9a6b02775aa1013c40d71712635a37c5e35804ac4490504dbc030ac0
MD5 d4a4eadce52b888992098085b616dfc8
BLAKE2b-256 a665ba6edee914340ed3bdd1b020d71a5312903dc3237daf46c021630f2e4192

See more details on using hashes here.

Provenance

The following attestation bundles were made for starelements-0.1.2-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