HackerReposA FIELD GUIDE TO OPEN SOURCE
ProSponsor the shelf♧ 0
← Back to top all-time
developit
htm
JavaScript2018–2026
developit / htm

htm

Hyperscript Tagged Markup: JSX alternative using standard tagged templates, with compiler support.

44Distinct people
58Mentions
1Explicit recommenders
9.1KGitHub stars
REPOSITORY AT A GLANCE

What to know before exploring

What it is

Hyperscript Tagged Markup: JSX alternative using standard tagged templates, with compiler support.

GitHub

Where it is used

No documented product use was verified in the last source check.

What people are saying

I use htm (Hyperscript Tagged Markup) instead of JSX and I love it. It's just plain JS and works extremely similar to JSX: https://github.com/developit/htm

timdaub used a recommendation phrase. One indexed comment matches. This is a phrase match, not a verdict.

No classified warning is attached to this page. Read the discussions before treating silence as approval.

Also in these discussions

No other repository is repeatedly linked in the same discussions.

This week on Hacker News

Nov 7–Nov 14 UTC · Direct repository links and separately labeled related project stories. Indexed through 11/14/2026, 6:01:08 PM UTC.

No direct repository links were found in the indexed HN discussions over the past seven days. Older discussions are available below.

Mentions over time

HN mentions by year58 dated mentions

Observed mentions: 2018-11-21 00:58:59 UTC to 2026-08-05 05:42:02 UTC.

0510152018-01-01 00:00:00 UTC to 2019-01-01 00:00:00 UTC (exclusive): 8 mentions; 4 submissions, 4 comments20182019-01-01 00:00:00 UTC to 2020-01-01 00:00:00 UTC (exclusive): 7 mentions; 0 submissions, 7 comments2020-01-01 00:00:00 UTC to 2021-01-01 00:00:00 UTC (exclusive): 15 mentions; 1 submissions, 14 comments2021-01-01 00:00:00 UTC to 2022-01-01 00:00:00 UTC (exclusive): 6 mentions; 0 submissions, 6 comments2022-01-01 00:00:00 UTC to 2023-01-01 00:00:00 UTC (exclusive): 7 mentions; 1 submissions, 6 comments20222023-01-01 00:00:00 UTC to 2024-01-01 00:00:00 UTC (exclusive): 6 mentions; 0 submissions, 6 comments2024-01-01 00:00:00 UTC to 2025-01-01 00:00:00 UTC (exclusive): 3 mentions; 0 submissions, 3 comments2025-01-01 00:00:00 UTC to 2026-01-01 00:00:00 UTC (exclusive): 4 mentions; 0 submissions, 4 comments2026-01-01 00:00:00 UTC to 2027-01-01 00:00:00 UTC (exclusive): 2 mentions; 0 submissions, 2 comments. Incomplete calendar period.2026*

* An incomplete calendar period is marked with an outlined bar. Its count is not directly comparable with a full period.

Counts come from the repository’s dated source evidence. Index cutoff: 2026-11-02 08:16:59 UTC.

View mention counts and date ranges
UTC ranges include their start and exclude their end. Select a period to filter the source evidence.
PeriodStart (UTC)End (exclusive, UTC)MentionsSubmissionsComments
20182018-01-01 00:00:00 UTC2019-01-01 00:00:00 UTC844
20192019-01-01 00:00:00 UTC2020-01-01 00:00:00 UTC707
20202020-01-01 00:00:00 UTC2021-01-01 00:00:00 UTC15114
20212021-01-01 00:00:00 UTC2022-01-01 00:00:00 UTC606
20222022-01-01 00:00:00 UTC2023-01-01 00:00:00 UTC716
20232023-01-01 00:00:00 UTC2024-01-01 00:00:00 UTC606
20242024-01-01 00:00:00 UTC2025-01-01 00:00:00 UTC303
20252025-01-01 00:00:00 UTC2026-01-01 00:00:00 UTC404
2026 (partial)2026-01-01 00:00:00 UTC2027-01-01 00:00:00 UTC202

From the project README

An excerpt from the repository, fetched Nov 13, 2026.

Read the project’s introduction
HTM (Hyperscript Tagged Markup) htm is JSX-like syntax in plain JavaScript - no transpiler necessary. Develop with React/Preact directly in the browser, then compile htm away for production. It uses standard JavaScript Tagged Templates and works in all modern browsers. htm by the numbers: 🐣 < 600 bytes when used directly in the browser ⚛️ < 500 bytes when used with Preact (thanks gzip 🌈) 🥚 < 450 byte htm/mini version 🏅 0 bytes if compiled using babel-plugin-htm Syntax: like JSX but also lit The syntax you write when using HTM is as close as possible to JSX: Spread props: <div ...${props}> instead of <div {...props}> Self-closing tags: <div /> Components: <${Foo}> instead of <Foo> (where Foo is a component reference) Boolean attributes: <div draggable /> Improvements over JSX htm actually takes the JSX-style syntax a couple steps further! Here's some ergonomic features you get for free that aren't present in JSX: No transpiler necessary HTML's optional quotes: <div class=foo> Component end-tags: <${Footer}>footer content<//> Syntax highlighting and language support via the lit-html VSCode extension and vim-jsx-pretty plugin. Multiple root element (fragments): <div /><div /> Support for HTML-style comments: <div><!-- comment --></div> Installation htm is published to npm, and accessible via the unpkg.com CDN: via npm: npm i htm hotlinking from unpkg: (no build tool needed!) import htm from 'https://unpkg.com/htm?module' const html = htm.bind(React.createElement); // just want htm + preact in a single file? there's a highly-optimized version of that: import { html, render } from 'https://unpkg.com/htm/preact/standalone.module.js' Usage If you're using Preact or React, we've included off-the-shelf bindings to make your life easier. They also have the added benefit of sharing a template cache across all modules. import { render } from 'preact'; import { html } from 'htm/preact'; render(html`<a href="/">Hello!</a>`, document.body); Similarly, for React: import ReactDOM from 'react-dom'; import { html } from 'htm/react'; ReactDOM.render(html`<a href="/">Hello!</a>`, document.body); Advanced Usage Since htm is a generic library, we need to tell it what to "compile" our templates to. You can bind htm to any function of the form h(type, props, ...children) (hyperscript). This function can return anything - htm never looks at the return value. Here's an example h() function that returns tree nodes: function h(type, props, ...children) { return { type, props, children }; } To use our custom h() function, we need to create our own html tag function by binding htm to our h() function: import htm from 'htm'; const html = htm.bind(h); Now we have an html() template tag that can be used to produce objects in the format we created above. Here's the whole thing for clarity: import htm from 'htm'; function h(type, props, ...children) { return { type, props, children }; } const html = htm.bind(h); console.log( html`<h1 id=hello>Hello world!</h1>` ); // { // type: 'h1', // props: { id: 'hello' }, // children: ['Hello world!'] // } If the template has multiple element at the root level the output is an array of h results: console.log(html` <h1 id=hello>Hello</h1> <div class=world>World!</div> `); // [ // { // type: 'h1', // props: { id: 'hello' }, // children: ['Hello'] // }, // { // type: 'div', // props: { class: 'world' }, // children: ['world!'] // } // ] Caching The default build of htm caches template strings, which means that it can return the same Javascript object at multiple points in the tree. If you don't want this behaviour, you have three options: Change your h function to copy nodes when needed. Add the code this[0] = 3; at the beginning of your h function, which disables caching of created elements. Use htm/mini, which disables caching by default. Example Curious to see what it all looks like? Here's a working app! It's a single HTML file, and there's no build or tooling. You can edit it with nano. <!DOCTYPE html> <html lang="en"> <title>htm Demo</title> <script type="module"> import { html, Component, render } from 'https://unpkg.com/htm/preact/standalone.module.js'; class App extends Component { addTodo() { const { todos = [] } = this.state; this.setState({ todos: todos.concat(`Item ${todos.length}`) }); } render({ page }, { todos = [] }) { return html` <div class="app"> <${Header} name="ToDo's (${page})" /> <ul> ${todos.map(todo => html` <li key=${todo}>${todo}</li> `)} </ul> <button onClick=${() => this.addTodo()}>Add Todo</button> <${Footer}>footer content here<//> </div> `; } } const Header = ({ name }) => html`<h1>${name} List</h1>` const Footer = props => html`<footer ...${props} />` render(html`<${App} page="All" />`, document.body); </script> </html> ⚡️ See live version ▶ ⚡️ Try this on CodeSandbox ▶ How nifty is that? Notice there's only one import - here we're using the prebuilt Preact integration since it's easier to import and a bit smaller. The same example works fine without the prebuilt version, just using two imports: import { h, Component, render } from 'preact'; import htm from 'htm'; const html = htm.bind(h); render(html`<${App} page="All" />`, document.body); Other Uses Since htm is designed to meet the same need as JSX, you can use it anywhere you'd use JSX. Generate HTML using vhtml: import htm from 'htm'; import vhtml from 'vhtml'; const html = htm.bind(vhtml); console.log( html`<h1 id=hello>Hello world!</h1>` ); // '<h1 id="hello">Hello world!</h1>' Webpack configuration via jsxobj: (details here) (never do this) import htm from 'htm'; import jsxobj from 'jsxobj'; const html = htm.bind(jsxobj); console.log(html` <webpack watch mode=production> <entry path="src/index.js" /> </webpac
Read the full README on GitHub

What Hacker News says

4 sources
Your example template and the others here are almost jsx after it's compiled (handwritten below). This jsx discussion seems more about removing the compile step, which you can do with https://github.com/developit/htm import { createElement as m } from "your-jsx-compatible-library"; var ListComponent = () => { let count = 0, selected = null; return { view: ({ attrs: { items }}) => m("div", null, m("p", null, "Clicked: " + count + " times"), m("ul", null, items.map((item) => m("li", { onclick: () => { count++; selected = item; }, style: { cursor: "pointer", color: item === selected ? "blue" : "black" }, }, item) )), selected && m("p", null, "Selected: " + selected) ) }; };
In Marko – A declarative, HTML‑based language
I explored JS decorators in the past but decorators are different in that they are a runtime operation that can't be statically compiled and can't be used on non-class properties. You probably know this already but macros on the other hand are just dumb expansions/computations of syntax transformed at compile time, like let result = add!(1 + 2); Would compile into let result = 3; Including macros into the JavaScript spec is a bit weird because it's an interpreted language so compile-time concepts don't really make sense (which is probably why decorators were proposed). But JavaScript is compiled more frequently than it isn't and we apply a crazy amount of transformations to it (typescript, tsx, path manipulation, styled-components, etc). IMO standardized compile-time manipulation with LSP integration would go a long way for lang
Read the rest
uage ergonomics. That would also mean transformations for things like jsx could be run _in the browser_ allowing people who don't want to use a bundler to run their code without one. // Removed by the bundler, can also be used in the browser import jsx from "react/jsx" const App = () => jsx!(<div>>Foo</div>) Projects like this : https://github.com/developit/htm are an expression/symptom of that need
In React is winning by default and slowing innovation
There are quite a lot of valid use cases to being able to transform arbitrary tokens into JavaScript at "compile" time. One that already exists is JSX, which is a macro that is baked into the TypeScript compiler but is restricted/tailored to React-style libraries. We sort of get around this today using template literals and eval, but it's janky. https://github.com/developit/htm A generic macro system could open the door to a framework like Svelte, Angular, Vue, etc being able to embed their template compilers (with LSP support) without wrapper compilers and IDE extensions. e.g. imagine syntax like this being possible (not saying it's good) ``` export class MyComponent { template = Vue.template!(<div>{{ this.foo }}</div>) #[Vue.reactive] foo = 'Hello World' constructor() { setTimeout(() => this.foo = 'Updated', 1000) } } svelte.i
Read the rest
nit(MyComponent, document.body) ``` Where the `template!` macro instructs the engine how to translate the tokens into their JavaScript syntax and the `#[reactive]` macro converts the class member into a getter/setter that triggers a re-render calculation. It would need to be adopted by TC39 of course and the expectation would be that, if provided at runtime, a JavaScript engine could handle the preprocessing however transpilers should be able to pre-compute the outputs so they don't need to be evaluated at runtime.
In Comptime.ts: compile-time expressions for TypeScript
> This is why both SolidJS and Vue Vapor both have helpers for rendering lists and conditional elements Haven't these tagged template libraries coalesced around the html`<${SomeComponent}><//>` syntax for custom components (including control flow components like Solid's `For` component)? The readme for Ryan Carniato's Lit DOM Expressions library includes this example for instance [1]: const view = html` <table class="table table-hover table-striped test-data"> <tbody> <${For} each=${() => state.data} >${row => html` <tr> <td class="col-md-1" textContent=${row.id} /> <td class="col-md-4"> <a onClick=${[select, row.id]}>${() => row.label}</a> </td> <td class="col-md-1"> <a onClick=${[remove, row.id]}
Read the rest
><span class="glyphicon glyphicon-remove" /></a> </td> <td class="col-md-6" /> </tr> `}<// > </tbody> </table> `; The author of the article mentions this very briefly, where he writes "For JSX-style references you would need to use binding syntax like <${MyComponent}>". The Preact author's htm tagged template library uses this convention as well [2]. [1] https://github.com/ryansolid/dom-expressions/tree/7fd9f86f1b... [2] https://github.com/developit/htm
In What should a native DOM templating API look like?

MAKE YOURSELF AT HOME

A few shortcuts

/
Search the collection
?
Open this guide
Esc
Close dialogs and navigation

Your saved repositories and interests stay in this browser.