Skip to content

feat: add native single-file HTML components - #1207

Open
JovanVeljanoski wants to merge 1 commit into
widgetti:masterfrom
JovanVeljanoski:feat_html_template
Open

JovanVeljanoski wants to merge 1 commit into
widgetti:masterfrom
JovanVeljanoski:feat_html_template

Conversation

@JovanVeljanoski

@JovanVeljanoski JovanVeljanoski commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR adds @solara.component_html("greeting.html") as another way to build components for standalone Solara apps. One file holds the component’s HTML template, optional scoped CSS, and optional browser JavaScript. HTML components can be used alongside existing Vue components.

Why

Solara already provides a useful connection between Python state and a browser UI. Today, custom component templates use Vue. For many components, plain HTML, CSS, and JavaScript are enough, and they let developers work directly with browser standards without adding a frontend build step.

This is especially helpful when developing a frontend with AI assistance. It is easy to generate and iterate on a self-contained HTML and CSS prototype, then connect only the values and actions that need Python. Interactions such as draft inputs, validation, and focus handling can stay in the component’s JavaScript until the developer chooses to synchronize them.

Keeping markup, styles, and behavior together also makes small visual components easier to reuse. Each component’s CSS is scoped, while shared CSS and JavaScript can still be imported where needed.

What this adds

A Python component declares its synchronized properties and callbacks:

@solara.component_html("greeting.html")
def Greeting(name="World", on_name=None, event_reset=None, children=None):
    pass

The adjacent HTML file contains the browser view:

<template>
  <div class="greeting">
    <label>Name <input data-solara-model="name"></label>
    <p>Hello, <strong data-solara-text="name"></strong>!</p>
    <button type="button" data-solara-event-click="reset">Reset</button>
    <slot></slot>
  </div>
</template>

<style>
  :host { display: block; }
  .greeting { padding: 1rem; border-radius: 0.5rem; }
</style>

<script type="module">
  export function mount({root, set}) {
    const input = root.querySelector("input");
    const onKeydown = event => {
      if (event.key === "Escape") set("name", "World");
    };
    input.addEventListener("keydown", onKeydown);
    return () => input.removeEventListener("keydown", onKeydown);
  }
</script>

data-solara-model updates the synchronized name property and calls on_name in Python. The reset button invokes the separate event_reset action. The optional mount function can use get, set, subscribe, and emit for more specific behavior—for example, keeping an input draft in JavaScript and sending it to Python only on blur, Enter, or after a pause in typing.

Implementation notes

  • HTML components use a native widget view and Shadow DOM. Their templates are not compiled or rendered by Vue.
  • A small set of data-solara-* bindings covers text, attributes, DOM properties, form values, and events. The template does not introduce a new expression language.
  • Python widget children, including Vue-backed components, can be placed in the default <slot>.
  • Optional CSS and JavaScript are extracted from the component file and served from content-addressed, same-origin URLs by both Flask and Starlette. Each component instance runs its own controller and cleanup function.
  • The signature-to-widget logic is shared with component_vue; the existing Vue API and behavior remain in place.
  • Templates reject inline event attributes and embedded scripts. Dynamic bindings restrict the DOM attributes and properties they can set, check URL schemes, and insert prop values as text rather than HTML.
  • API documentation, an example, and a design note are included.

Scope

This first version supports standalone Solara apps. Notebook support is outside its scope. The default page shell still uses Vue, so this adds a native HTML component option rather than removing Vue from Solara.

Verification

  • pre-commit run --all-files passed, including Ruff, codespell, and mypy checks for ipyvuetify 1 and 3.
  • Python 3.12 unit suite: 518 passed, 27 skipped.
  • New Chromium integration tests: 8 passed with ipywidgets 8 and 8 passed with ipywidgets 7, across Flask and Starlette. They cover two-way communication, delayed commits, mixed HTML and Vue children, scoped CSS, public assets under a URL prefix, and cleanup during asynchronous module loading.
  • The Solara wheel built successfully and includes the new runtime files.

The full repository browser CI matrix still needs to run on the PR. A broader local integration run in an environment that differs from CI was not fully green, so the targeted results above are not a claim that every existing integration test passes.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant