--- title: "Reactive HTML" author: "@chris" author_url: https://elements.dev/u/@chris published: 2026-11-07T00:45:49.440Z url: https://elements.dev/feed/reactive-html-ssy8d08n kind: post format: article --- # Reactive HTML by [@chris](https://elements.dev/u/@chris) ยท 2026-11-07 ## Description A tour of Elements HTML: templates, constructor attributes, reactive bindings, two-way forms and directives. Elements HTML is plain HTML that's reactive and type checked. No JSX, no script tags, no state code: you write markup, bind it to data with `{}`, and the page updates when the data changes. I started sketching this syntax in 2012, before anything else in Elements existed. Here's a tour of the language, the design decisions behind it, and why it turns out agents write it well too. Here's a very simple HTML page. ```ehtml app/pages/home/template.ehtml import "./style.css";

Hello World!

``` First, notice this looks like regular HTML, with a TypeScript import statement in the file, which happens to be importing a stylesheet. The file is just regular HTML with TypeScript allowed in the same file, anywhere outside of the templates. No script tags needed. Any top level tag in the file we call a *template*. The template you're seeing above is the special `html` template and defines a page that can be rendered from a url in a browser. The html template is special, and is the default export of the `ehtml` file. The style import automatically links the stylesheet into the page so that it's styled, with no configuration required. You create named templates in the same file, or import them from other files. You put the template in the top scope of the file with a `TitleCase` name like ``, and you can invoke a template using the same tag inside the `html` or another child template. This allows you to organize your HTML markup into reusable templates, which is helpful for not having to redefine the same looks and behaviors again and again across your application. ```ehtml app/pages/home/template.ehtml import "./style.css";

Hello World!

``` The HTML language has been extended to allow you to bind your HTML to reactive data, which allows the UI to render dynamic values and update automatically when those values change. ```ehtml app/pages/home/template.ehtml import "./style.css";

{greeting}

``` We've defined one public constructor attribute called `greeting` on the html template. It's of type `string`, and its default value is "Hello World!". We've defined the same constructor attribute on the `Welcome` template and threaded the value through from the html template to the child template. We use that *constructor attribute* either in the body of the template or in attribute values using *expression block* syntax denoted with the curly `{}` braces. You can put any TypeScript expression inside of these expression blocks, and any constructor attributes are lexically scoped and available for use, just as a function parameter would be inside a function. For the uninitiated, "lexically scoped" essentially means if you can see it you can use it. Constructor attributes are made reactive automatically. The constructor attribute values can come from a route function, which can fill those values from a database call, a url variable, or another source. In this example, let's assume we want the value of greeting to be set by a url query parameter like `https://elements.dev?greeting=hello+world`. ```ts index.ts import home from "#app/pages/home"; app.route("/", home); ``` ```ts app/pages/home/index.ts import { Request, Response } from "@elements/app"; import html from "./template"; export default function route(req: Request, res: Response) { return new html({ greeting: req.params.greeting }); } ``` ```ehtml app/pages/home/template.ehtml import "./style.css";

{greeting}

``` This code shows a route function, which is called when a user goes to the `/` url. The route function invokes a new instance of the `html` template, passing an object of key value pairs. We call this object the *constructor attributes*. If a user visits the `/` url in the browser, the page will initially be rendered on the server. And then the HTML runtime will attach to the server rendered markup when the page loads in the browser. This gives you good SEO, a fast first page paint, and all the benefits of a reactive, dynamic UI for the user. It's convenient to build UIs this way because we don't have to write a lot of boilerplate code to update the UI in response to data changes. The HTML runtime can do this for us automatically, which is easier for humans to reason about. But perhaps surprisingly, it's also easier for AI to reason about. If you open this page in a browser, you can experiment with changing the value by opening up the JavaScript console and using the special `$` value to change the html constructor attribute values. This is very helpful when building to test out different states in your UI. ```js browser console > $.greeting = "Boom!"; ``` If you type that into the JavaScript console in the browser, the UI will update instantly with the new value. We say the UI reflects the state of the data automatically. And the runtime takes care of doing this in the most efficient way possible. All you have to worry about is declaring your UI and data bindings in markup and the runtime takes care of the rest. You can declare private constructor attributes as well, which are not settable outside of the template and can be changed inside event handlers. ```ehtml app/pages/home/template.ehtml ``` This example shows an inline click handler that toggles the private active attribute as the user clicks the button. Next we'll add a CSS class change to reflect the toggled value. ```ehtml app/pages/home/template.ehtml ``` This adds the `toggle-active` class to the button if the `active` variable is set to `true`, and removes it if the variable is `false`. Form inputs are two-way bound automatically such that if the UI changes, the data value changes, and if the data value changes, the UI changes. ```ehtml app/pages/home/template.ehtml export interface User { name: string; }
``` If the `user.name` changes in the input field, the `$.user.name` data field will change. And vice versa. This example also illustrates the use of a TypeScript interface to define the type of a constructor attribute. The Elements HTML language is built into the TypeScript compiler toolchain, which itself is built into the Elements toolchain. All of that is to say: your HTML is fully type checked. If you provide an attribute to a template that is not recognized, or of the wrong type, you get a compiler error. If you fail to provide required parameters, or try to provide a private attribute, you get compiler errors. The other benefit of being tightly integrated with TypeScript is deep symbol binding. If you're in your editor you can jump to template symbol references and find all references to a template by using the editor tools, for example. Find and replace is simple since Elements HTML templates are just symbols, like any function or class. A few extra attributes called *directives* were created to enable loops and conditional rendering. The directives start with the `e:` prefix and you'll mostly want `e:if` and `e:for`. You may also choose to use `e:switch`. ```ehtml app/pages/home/template.ehtml
    0}>
  • {item.description}
No items yet.
``` I originally designed the syntax to be as close to HTML as possible, because I thought it was easier on designers and developers alike to reason about the interfaces this way. But I also wanted the benefit of TypeScript type checking, something that React developers will appreciate about TSX/JSX. It turns out you can have the best of both worlds with some compiler work. The rule is that you can have TypeScript code at the top level, like any other `.ts` file. Once you're in a template, marked by the `` syntax, you're in the HTML syntax which includes the `(...)` extension for constructor attributes, and the `{}` extension for reactive TypeScript expressions. Sticking with standard HTML syntax, and pushing all of the reactivity complexity into the runtime, where the user doesn't have to think about it at all, reduces cognitive load. But it turns out it's better for agents too! Rather than having to reason about higher-order complexity of transitions, rendering performance, and other higher level abstractions, they simply think about the markup and what data is needed for each template. It turns out, even with no premade templates or "ecosystem", the agents can do incredibly well with just this reactive HTML primitive! But, I do believe there is some value to not reinventing the wheel again and again on standard templates and behaviors. For this reason, we built `@elements/ui` which is installed in new apps automatically, and contains the base style system and about 80 of the most common, and complex, templates you can find on the web. You can browse that system [here](https://elements.dev/ui). You can learn more about the Elements HTML language by reading [the manual](https://elements.dev/man/html). Or you can install Elements and simply ask your AI agent about it! I think it's a great way for both humans and AI agents to write interfaces today. Let me know in the comments if you have any questions!