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

6 sources
This seems like a case of caring way too much about something that's hardly very different. JSX versus tagged template strings can be incredibly similar to one another. The examples in this article are using vanilla template strings to author raw html, but that only misses a couple of nicities JSX has. There are tagged template string libraries like htm[1] that do include some of the few nicities JSX has, but which are actually compatible with the official language. [1] https://github.com/developit/htm
In Simple Modern JavaScript Using JavaScript Modules and Import Maps

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.