Getting started
Installation
Start a new HTMX UI site with one command, or add the components to an app that already uses Tailwind CSS v4 and htmx.
Commands are shown for bun, npm, pnpm and yarn. Pick yours once; every command block on the site remembers the choice.
Packages#
| Package | What it is |
|---|---|
htmx-ui | The components: CSS classes, design tokens, dark mode and small behaviours. Works with any bundler. |
htmx-ui-engine | Builds a site from Nunjucks pages: htmx-ui dev, build and preview, on Bun or Node. See Engine. |
create-htmx-ui | Scaffolds a new site that uses both. |
htmx-ui-upgrade | Not installed with the project: run it when you update, and it rewrites what the release changed. See Upgrading. |
Requirements#
| Dependency | Version | Why |
|---|---|---|
| Tailwind CSS | 4.x | Component styles use @apply and @theme. |
| htmx | 4.x | Fragment swaps; components initialise inside new content. |
| Bun or Node | Bun 1.2.3+ / Node 22.12+ | For the engine. With your own bundler, any that handles TypeScript and CSS imports works. |
New project#
Create a site, install it and start the dev server:
bun create htmx-ui my-site
cd my-site
bun install
bun run dev
npm create htmx-ui my-site
cd my-site
npm install
npm run dev
pnpm create htmx-ui my-site
cd my-site
pnpm install
pnpm run dev
yarn create htmx-ui my-site
cd my-site
yarn install
yarn run dev
You get pages/ (one file per route), a layout with a theme toggle, Tailwind with the library's styles,
an htmx button talking to a mock endpoint, and dev, build and preview scripts.
A site created with bun runs on Bun; with npm, pnpm or yarn it runs on Node, with Vite. Open
http://localhost:3000 and edit pages/index.html.
Add the engine to a project#
1. Install#
bun add htmx-ui htmx.org
bun add -d htmx-ui-engine tailwindcss
npm install htmx-ui htmx.org
npm install -D htmx-ui-engine tailwindcss
pnpm add htmx-ui htmx.org
pnpm add -D htmx-ui-engine tailwindcss
yarn add htmx-ui htmx.org
yarn add -D htmx-ui-engine tailwindcss
Then the Tailwind integration for the runtime you'll build with. On Bun:
bun add -d bun-plugin-tailwind
On Node (npm, pnpm or yarn), Vite and its Tailwind plugin:
bun add -d vite @tailwindcss/vite
npm install -D vite @tailwindcss/vite
pnpm add -D vite @tailwindcss/vite
yarn add -D vite @tailwindcss/vite
2. Add the scripts#
{
"scripts": {
"dev": "htmx-ui dev",
"build": "htmx-ui build",
"preview": "htmx-ui preview"
}
}
3. Import the styles#
Import Tailwind first, then the library. The @source line tells Tailwind where your own markup lives.
@import "tailwindcss";
@import "htmx-ui/styles.css";
@source "./";
4. Initialise components#
import "htmx.org";
import "./styles.css";
import { initComponents } from "htmx-ui";
import { initTheme } from "htmx-ui/theme";
document.addEventListener("DOMContentLoaded", () => {
initTheme();
initComponents(document);
});
// htmx 4 fires htmx:after:process on each newly inserted element
document.addEventListener("htmx:after:process", (e) => {
initComponents((e.target as ParentNode) ?? document);
});
5. Add a layout and a page#
asset() makes the script path relative to whichever page is rendering, so the bundler finds it from any depth.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% block title %}My site{% endblock %}</title>
<script type="module" src="{{ asset('app.ts') }}"></script>
</head>
<body class="bg-background text-foreground">
{% block content %}{% endblock %}
</body>
</html>
{% extends "layouts/base.html" %}
{% block content %}<button class="btn btn-primary">It works</button>{% endblock %}
bun run dev
npm run dev
pnpm run dev
yarn run dev
Existing project#
If the app already has Tailwind v4, htmx and a bundler, it only needs the components:
bun add htmx-ui
npm install htmx-ui
pnpm add htmx-ui
yarn add htmx-ui
- Add
@import "htmx-ui/styles.css";directly after@import "tailwindcss";. - Call
initComponents()on load and onhtmx:after:process, as in step 4 above.
--primary, --border and --radius on :root,
and Tailwind colours with the same names. If your app already defines these, the definition loaded last wins. Rename yours,
or set them to the values you want (see Theming).
Already using the dark: variant?#
The library switches Tailwind's dark: variant from the OS media query to a
.dark class on <html>. If your app relies on the media-query
behaviour, read Dark mode before upgrading.
Upgrading later#
A release before 1.0 can rename a class or change a component's markup — each one listed under
Breaking changes in the changelog. You don't have to apply them by
hand: htmx-ui-upgrade rewrites your templates, scripts, stylesheets and config to the release you
are moving to, and prints the places it left for you to check. It refuses to run on uncommitted changes, so an
upgrade is one diff to review:
bunx htmx-ui-upgrade@latest
npx htmx-ui-upgrade@latest
pnpm dlx htmx-ui-upgrade@latest
yarn dlx htmx-ui-upgrade@latest
Then install the new versions and build. What it changes, its options and how to check the result are on Upgrading.
What the package exports#
JavaScript ships as ES modules with TypeScript types. htmx.org and tailwindcss are peer dependencies, so your app controls their versions.
| Import | Contents |
|---|---|
htmx-ui | initComponents(root): wires up every component's behaviour under root. |
htmx-ui/styles.css | Design tokens, the dark variant, .prose (Tailwind Typography, installed with the package), syntax-highlighting colours and every component's styles. |
htmx-ui/theme | initTheme() and getTheme() for the light/dark switcher. |
htmx-ui/icons/<name>.svg | The SVG icons, for your own templates or <img> tags. |
htmx-ui/components/<name>/<name>.html | Nunjucks macros (icon, code, alert, spinner). The engine resolves them by name: {% from "components/icon/icon.html" import icon %}. |