{"id":20513822,"url":"https://github.com/webreflection/uhtml","last_synced_at":"2026-01-25T07:35:44.927Z","repository":{"id":37964887,"uuid":"240760595","full_name":"WebReflection/uhtml","owner":"WebReflection","description":"A micro HTML/SVG render","archived":false,"fork":false,"pushed_at":"2024-11-27T10:53:35.000Z","size":1366,"stargazers_count":968,"open_issues_count":13,"forks_count":40,"subscribers_count":18,"default_branch":"main","last_synced_at":"2025-02-23T13:01:59.484Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WebReflection.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-02-15T17:45:33.000Z","updated_at":"2025-02-22T04:51:41.000Z","dependencies_parsed_at":"2023-12-29T23:32:35.536Z","dependency_job_id":"22f8e5ca-864c-4440-b4f0-0874a1f54ee3","html_url":"https://github.com/WebReflection/uhtml","commit_stats":{"total_commits":416,"total_committers":8,"mean_commits":52.0,"dds":"0.033653846153846145","last_synced_commit":"2fdffc4d7fcb775c77fa6d3fba843154d7201f13"},"previous_names":[],"tags_count":171,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fuhtml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fuhtml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fuhtml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fuhtml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WebReflection","download_url":"https://codeload.github.com/WebReflection/uhtml/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242117719,"owners_count":20074438,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-15T21:13:21.197Z","updated_at":"2026-01-25T07:35:44.921Z","avatar_url":"https://github.com/WebReflection.png","language":"HTML","readme":"# uhtml\n\n[![Downloads](https://img.shields.io/npm/dm/uhtml.svg)](https://www.npmjs.com/package/uhtml) [![build status](https://github.com/WebReflection/uhtml/actions/workflows/node.js.yml/badge.svg)](https://github.com/WebReflection/uhtml/actions) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/uhtml/badge.svg?branch=main)](https://coveralls.io/github/WebReflection/uhtml?branch=main) [![CSP strict](https://webreflection.github.io/csp/strict.svg)](https://webreflection.github.io/csp/#-csp-strict)\n\n![snow flake](./uhtml-head.jpg)\n\n\u003csup\u003e**Social Media Photo by [Andrii Ganzevych](https://unsplash.com/@odya_kun) on [Unsplash](https://unsplash.com/)**\u003c/sup\u003e\n\n- - -\n\n## Warning ⚠️\n\nI'm on vacation! The fastest version and most battle tested version of this library is **v4**.\n\nIf you are using **v4**, please keep doing that!\n\nIf you're happy to try **v5** please file issues in here, don't expect me to react out of tweets, and thank you for helping me out with use cases I couldn't even think about.\n\n**v5** is a rewrite from scratch based on another library (which is *Python based*) that works perfectly fine **but** it doesn't have a reactivity story fully attached yet.\n\nThis rewrite feels good, it avoids unnecessary loops, but it's also naively based on *signals* for everything that was way easier to control before ... *a whole render* each time, never atomic, never considering edge cases around conditional arrays and what not.\n\nI understand, now that signals are in, everyone is going to use signals for everything, as a distributed shared state of everything you are doing, but as a person that alaywas provided libraries to keep it simple, I couldn't even think about some of the scenarios you are \"*abusing*\" (no offence, my shortsighting) signals for, so my deepest apologies if the current state of **v5** cannot meet your expectations, I've tried my best, and unfortunately rushed a little bit, with this release, but all the ideas behind represent where I want to go from now on.\n\nAgain, apologies for not delivering like I've done before but be assured all the dots will be soon connected in a better way, or at least one that works reliably 👋\n\nP.S. **v4** is right here: https://github.com/WebReflection/uhtml/tree/v4\n\n- - -\n\nA minimalistic library to create fast and reactive Web pages.\n\n```html\n\u003c!doctype html\u003e\n\u003cscript type=\"module\"\u003e\n  import { html } from 'https://esm.run/uhtml';\n\n  document.body.prepend(\n    html`\u003ch1\u003eHello DOM !\u003c/h2\u003e`\n  );\n\u003c/script\u003e\n```\n\n*uhtml* (micro *µ* html) offers the following features without needing specialized tools:\n\n  * *JSX* inspired syntax through template literal `html` and `svg` tags\n  * *React* like components with *Preact* like *signals*\n  * compatible with native custom elements and other Web standards out of the box\n  * simplified accessibility via `aria` attribute and easy *dataset* handling via `data`\n  * developers enhanced mode runtime debugging sessions\n\n\n\u003csub\u003etest in [codepen](https://codepen.io/WebReflection/pen/VYvbddv?editors=0010)\u003c/sub\u003e\n\n```js\nimport { html, signal } from 'https://esm.run/uhtml';\n\nfunction Counter() {\n  const count = signal(0);\n\n  return html`\n    \u003cbutton onClick=${() =\u003e count.value++}\u003e\n      Clicked ${count.value} times\n    \u003c/button\u003e\n  `;\n}\n\ndocument.body.append(\n  html`\u003c${Counter} /\u003e`\n);\n```\n\n- - -\n\n## Syntax\n\nIf you are familiar with *JSX* you will find *uhtml* syntax very similar:\n\n  * self closing tags, such as `\u003cp /\u003e`\n  * self closing elements, such as `\u003ccustom-element\u003e...\u003c/\u003e`\n  * object spread operation via `\u003c${Component} ...${{any: 'prop'}} /\u003e`\n  * `key` attribute to ensure *same DOM node* within a list of nodes\n  * `ref` attribute to retrieve the element via effects or by any other mean\n\nThe main difference between *uhtml* and *JSX* is that *fragments* do **not** require `\u003c\u003e...\u003c/\u003e` around:\n\n```js\n// uhtml fragment example\nhtml`\n  \u003cdiv\u003efirst element\u003c/div\u003e\n  \u003cp\u003e ... \u003c/p\u003e\n  \u003cdiv\u003elast element\u003c/div\u003e\n`\n```\n\n### Special Attributes\n\nOn top of *JSX* like features, there are other attributes with a special meaning:\n\n  * `aria` attribute to simplify *a11y*, such as `\u003cbutton aria=${{role: 'button', labelledBy: 'id'}} /\u003e`\n  * `data` attribute to simplify *dataset* handling, such as `\u003cdiv data=${{any: 'data'}} /\u003e`\n  * `@event` attribute for generic events handling, accepting an array when *options* are meant to be passed, such as `\u003cbutton @click=${[event =\u003e {}, { once: true }]} /\u003e`\n  * `on...` prefixed, case insensitive, direct events, such as `\u003cbutton onclick=${listener} /\u003e`\n  * `.direct` properties access, such as `\u003cinput .value=${content} /\u003e`, `\u003cbutton .textContent=${value} /\u003e` or `\u003cdiv .className=${value} /\u003e`\n  * `?toggle` boolean attributes, such as `\u003cdiv ?hidden=${isHidden} /\u003e`\n\nAll other attributes will be handled via standard `setAttribute` or `removeAttribute` when the passed value is either `null` or `undefined`.\n\n### Special Elements\n\nElements that contain *data* such as `\u003cscript\u003e` or `\u003cstyle\u003e`, or those that contains text such as `\u003ctextarea\u003e` require *explicit closing tag* to avoid having in between templates able to break the layout.\n\nThis is nothing new to learn, it's just how the Web works, so that one cannot have `\u003c/script\u003e` within a `\u003cscript\u003e` tag content and the same applies in here.\n\nIn *debugging* mode, an error telling you which template is malformed will be triggered in these cases.\n\n### About Comments\n\nUseful for developers but never really relevant for end users, *comments* are ignored by default in *uhtml* except for those flagged as \"*very important*\".\n\nThe syntax to preserve a comment in the layout is `\u003c!--! important !--\u003e`. Every other comment will not be part of the rendered tree.\n\n```js\nhtml`\n  \u003c!--! this is here to stay !--\u003e\n  \u003c!--// this will go --\u003e\n  \u003c!-- also this --\u003e\n`\n```\n\nThe result will be a clear `\u003c!-- this is here to stay --\u003e` comment in the layout without starting and closing `!`.\n\n#### Other Comments\n\nThere are two kind of \"*logical comments*\" in *uhtml*, intended to help its own functionality:\n\n  * `\u003c!--◦--\u003e` *holes*, used to *pin* in the DOM tree where changes need to happen.\n  * `\u003c!--\u003c\u003e--\u003e` and `\u003c!--\u003c/\u003e--\u003e` persistent *fragments* delimeters\n\nThe *hole* type might disappear once replaced with different content while persistent fragments delimeters are needed to confine and/or retrieve back fragments' content.\n\nNeither type will affect performance or change layout behavior.\n\n- - -\n\n## Exports\n\n```js\nimport {\n  // DOM manipulation\n  render, html, svg, unsafe,\n  // Preact like signals, based on alien-signals library\n  signal, computed, effect, untracked, batch,\n  // extras\n  Hole, fragment,\n} from 'https://esm.run/uhtml';\n```\n\n**In details**\n\n  * `render(where:Element, what:Function|Hole|Node)` to orchestrate one-off or repeated content rendering, providing a scoped *effect* when a *function* is passed along, such as `render(document.body, () =\u003e App(data))`. This is the suggested way to enrich any element content with complex reactivity in it.\n  * `html` and `svg` [template literal tags](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) to create either *HTML* or *SVG* content.\n  * `unsafe(content:string)` to inject any content, even *HTML* or *SVG*, anywhere within a node: `\u003cdiv\u003e${unsafe('\u003cem\u003evalue\u003c/em\u003e')}\u003c/div\u003e`\n  * `signal`, `computed`, `effect`, `untracked` and `batch` utilities with [Preact signals](https://github.com/preactjs/signals/blob/main/packages/core/README.md) inspired API, fueled by [alien-signals](https://github.com/stackblitz/alien-signals#readme)\n  * `Hole` class used internally to resolve `html` and `svg` tags' template and interpolations. This is exported mainly to simplify *TypeScript* related signatures.\n  * `fragment(content:string, svg?:boolean)` extra utility, used internally to create either *HTML* or *SVG* elements from a string. This is merely a simplification of a manually created `\u003ctemplate\u003e` element, its `template.innerHTML = content` operation and retrieval of its `template.content` reference, use it if ever needed but remember it has no special meaning or logic attached, it's literally just standard DOM fragment creation out of a string.\n\n- - -\n\n## Loading from a CDN\n\nThe easiest way to start using *uhtml* is via *CDN* and here a few exported variants:\n\n```js\n// implicit production version\nimport { render, html } from 'https://esm.run/uhtml';\n// https://cdn.jsdelivr.net/npm/uhtml/dist/prod/dom.js\n\n// explicit production version\nimport { render, html } from 'https://esm.run/uhtml/prod';\n// https://cdn.jsdelivr.net/npm/uhtml/dist/prod/dom.js\n\n// explicit developer/debugging version\nimport { render, html } from 'https://esm.run/uhtml/dev';\nimport { render, html } from 'https://esm.run/uhtml/debug';\n// https://cdn.jsdelivr.net/npm/uhtml/dist/dev/dom.js\n\n// automatic prod/dev version on ?dev or ?debug\nimport { render, html } from 'https://esm.run/uhtml/auto';\nimport { render, html } from 'https://esm.run/uhtml/cdn';\n// https://cdn.jsdelivr.net/npm/uhtml/dist/prod/cdn.js\n```\n\nUsing `https://esm.run/uhtml/cdn` (or */auto*) or the fully qualified `https://cdn.jsdelivr.net/npm/uhtml/dist/prod/cdn.js` URL provides an automatic switch to *debug* mode if the current page location contains `?dev` or `?debug` or `?debug=1` query string parameter plus it guarantees the library will not be imported again if other scripts use a different *CDN* that points at the same file in a different location.\n\nThis makes it easy to switch to *dev* mode by changing the location from `https://example.com` to `https://example.com?debug`.\n\nLast, but not least, it is not recommended to bundle directly *uhtml* in your project because components portability becomes compromised, as example, if each component bundles within itself *uhtml*.\n\n### Import Map\n\nAnother way to grant *CDN* and components portability is to use an import map and exclude *uhtml* from your bundler.\n\n```html\n\u003c!-- defined on each page --\u003e\n\u003cscript type=\"importmap\"\u003e\n{\n  \"imports\": {\n    \"uhtml\": \"https://cdn.jsdelivr.net/npm/uhtml/dist/prod/cdn.js\"\n  }\n}\n\u003c/script\u003e\n\u003c!-- your library code --\u003e\n\u003cscript type=\"module\"\u003e\nimport { html } from 'uhtml';\n\ndocument.body.append(\n  html`Import Maps are Awesome!`\n);\n\u003c/script\u003e\n```\n\n- - -\n\n## Extra Tools\n\nMinification is still recommended for production use cases and not only for *JS*, also for the templates and their content.\n\nThe [rollup-plugin-minify-template-literals](https://www.npmjs.com/package/rollup-plugin-minify-template-literals) is a wonderful example of a plugin that does not complain about *uhtml* syntax and minifies to its best *uhtml* templates in both *vite* and *rollup*.\n\nThis is a *rollup* configuration example:\n\n```js\nimport terser from \"@rollup/plugin-terser\";\nimport templateMinifier from \"rollup-plugin-minify-template-literals\";\nimport { nodeResolve } from \"@rollup/plugin-node-resolve\";\n\nexport default {\n  input: \"src/your-component.js\",\n  plugins: [\n    templateMinifier({\n      options: {\n        minifyOptions: {\n          // allow only explicit \u003c!--! comments !--\u003e\n          ignoreCustomComments: [/^!/],\n          keepClosingSlash: true,\n          caseSensitive: true,\n        },\n      },\n    }),\n    nodeResolve(),\n    terser(),\n  ],\n  output: {\n    esModule: true,\n    file: \"dist/your-component.js\",\n  },\n};\n```\n\n- - -\n\n## About SSR and hydration\n\nThe current *pareser* is already environment agnostic, it runs on the client like it does in the server without needing dependencies at all.\n\nHowever, the current *SSR* story is still a **work in progress** but it's planned to land sooner than later.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebreflection%2Fuhtml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebreflection%2Fuhtml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebreflection%2Fuhtml/lists"}