Declarative HTML Components in plain terms
This proposal is a way to build web interfaces by writing markup. You define reusable tags, add data and behavior with a small set of attributes, and it turns into ordinary native HTML the browser already understands. This page is the plain-language tour; every section links to the precise spec.
Orientation · read this firstThe one-sentence version
Take the good parts of modern component frameworks, components, reactive data, scoped styles, and express them as HTML itself, so they run natively in the browser (through a polyfill today) or compile to React, Vue, or Svelte, with no build step required and no eval().
<!-- You write this -->
<x-button variant="solid">Save</x-button>
<!-- It becomes a real native button, in the light DOM -->
<button component="x-button">Save</button>A component is a piece of markup, treated as data. The browser (or the polyfill) reads it and produces real elements, so what ships is a plain <button> with native focus, forms, and accessibility intact, plus a component stamp saying where it came from.
The pieces, at a glance
Declarative HTML Components is one specification, in chapters that each define part of the component model. In plain terms:
| Chapter | In plain terms |
|---|---|
| Components | Define a reusable tag with <template component>. Its interface (props) and non-visual declarations, including what it adds to the document head, live in a <defs> region; below that is the markup it renders. |
| Templating | Control flow uses attributes that survive the HTML parser: $if (show or not), $each (repeat), $match (pick one). Output uses {expression} for inline text and $value/$html to replace content with text or safe markup. |
| Expressions | A small, typed expression language for bindings and inline text, with predictable rules for missing data and equality (evaluation always recovers). |
| Bindings & events | Set reactive values with from:attr, two-way with bind:, react to events with on:, toggle a class or style with class:/style:. |
| Reactivity | Declare local <state>, derived <computed> values, and external <data> resources. Reads refetch and synchronized writes send according to their declared change policy. |
| Types | Plain-keyword value types cover strings, numbers, HTML value formats, and selected CSS values. An omitted prop with no default is null. |
| Validation | A declared type is a constraint: a typed prop or data value that fails its type is invalid, through the validity model HTML Forms extends to every element, and styled with the native :user-invalid. |
| Style scoping | A component's <style> applies to that component, using CSS @scope, scoping without the isolation cost of a shadow root. |
| The JavaScript layer | The escape hatch. When behavior genuinely needs code (a timer, a chart library), a separate ES-module controller attaches to the component. Definitions contain no code; they reference it. |
How it fits together
Two ideas hold the whole thing up:
- Declarative by default, code only when needed. Most of an interface, structure, data, styling, is plain markup. Imperative JavaScript is a deliberate, separate layer you reach for only when there is no declarative way, and even then the component definition stays pure data.
- Everything joins by tag. A definition and, if it has one, a controller both register under the same tag name in a registry, the exact way the platform's own custom elements join a tag to a class. Nothing references anything else by file path; the tag is the join.
The same source has two honest execution paths: a polyfill that lowers it to native DOM in the browser today, and converters that compile it to idiomatic React, Vue, or Svelte. Both must produce the same observable result (Targets & Equivalence).
How "done" is measured
The specification advances through Levels, as CSS specifications do: this draft is Level 1, and every chapter belongs to it, with optional extensions marked where they appear. A Level describes the language contract. A dated Snapshot records the draft on a given day. An implementation should identify the snapshot it supports; conformance tests establish whether it meets that contract.
What the draft defines, and what is still open
This is a Stage 0 proposal. The chapters define the language; implementation coverage belongs in the reference tools.
Typed native-root components and slots, templating and control flow, expressions, bindings, live state and computed updates, reactive data resources, validation, style scoping, external composition, and the JavaScript layer. The browser runtime and compiled targets share one observable-equivalence contract.
Some hydration records remain unresolved, including explicit prop recovery and logical ownership through server-rendered portals (Rendered Form & Hydration). Untagged type unions and writable computed bindings remain explorations. Deeper native-element and Shadow DOM integration needs upstream platform work.
See it working
For complete, copy-pasteable code, one component, a component that uses another, and a JavaScript controller, see Examples. Then read any chapter above for the precise rules.