HTML Next in plain terms
HTML Next is a way to build web interfaces by writing markup, not framework code. 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 (no wrapper, no shadow root) -->
<button data-component="x-button" data-variant="solid">Save</button>A component is not a class or a function. It 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 data-component stamp saying where it came from.
The pieces, at a glance
HTML Next is not one big feature; it is a handful of small ones, each its own module. In plain terms:
| Module | In plain terms |
|---|---|
| Components | Define a reusable tag with <template component>. Its interface (props) and non-visual declarations live in a <defs> region; below that is the markup it renders. |
| Templating | Control flow as attributes that survive the HTML parser: $if (show or not), $each (repeat), $match (pick one), $value/$html (output text or safe markup). |
| Expressions | A small, typed, no-JavaScript expression language for the values in those attributes, with predictable rules for missing data and equality (it never throws). |
| Bindings & events | Set values with :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> reads. Change a value and only what depends on it updates. |
| Types | Props default to strings but can borrow richer web-native types from CSS (<length>, <color>, enums, lists, structured JSON). |
| Validation | The browser's own form-validation model, generalized so any typed value can be valid or invalid, 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 stay script-free. |
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 markup with no script. 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
There is no big version number. Like CSS, each module advances on its own through Levels (Level 1 is the shipping baseline), and a dated Snapshot is just a named set of modules each pinned at a Level. A feature is real when it is specified and has conformance tests, not when a number is announced.
What is solid, and what is still open
Being plain also means being honest about maturity:
The component model, templating and control flow, the expression language and its value semantics, bindings, types, style scoping, and the shape of the JavaScript layer, all specified, and the browser-lowering and validation paths have a tested reference implementation.
Live reactive updates in the browser (a later Level), real-time data (SSE/WebSocket), routing, demand-driven lazy loading gated on reactive state, and the full converter targets. These are named as open, not hidden (details).
See it working
For complete, copy-pasteable code, one component, a component that uses another, and a JavaScript controller, see Examples. Then dive into any module above for the precise rules.