Getting started
Introduction
HTMX UI is a set of Tailwind CSS components for apps where the server renders HTML and htmx swaps it into the page.
Installation
Add the package to a new or existing Bun project.
Theming
Override design tokens or build a custom theme.
Dark mode
How dark mode works and how to add a switcher.
Components
Browse every component with live examples.
Upgrading
One command rewrites what a release renamed.
What it is#
Most component libraries assume a JavaScript framework owns the page. HTMX UI assumes the opposite: your server returns HTML, htmx swaps it in, and components need to look right and keep working when they arrive inside a fragment.
So every component is:
- Markup and classes. A button is
<button class="btn btn-primary">. Any server language can render it. - Styled with Tailwind v4. Component classes live in
@layer components, so a utility class on the same element always wins. - Themed with CSS variables. Colours and radius are variables like
--primaryand--radius, exposed to Tailwind asbg-primary,text-muted-foregroundand so on. - Behaviour only where needed. Things like copy buttons and the mobile sidebar are tiny TypeScript modules that re-run after every htmx swap.
A quick look#
Components compose like plain HTML. This card uses the card, badge and button components together:
Deploy preview
ReadyBuilt from main in 42 seconds.
<div class="card w-full max-w-sm">
<div class="card-header pb-6">
<div class="flex items-center justify-between">
<h3 class="card-title">Deploy preview</h3>
<span class="badge badge-success badge-dot">Ready</span>
</div>
<p class="card-description">Built from main in 42 seconds.</p>
</div>
<div class="card-footer pt-6">
<button class="btn btn-primary btn-sm">Open preview</button>
<button class="btn btn-ghost btn-sm">View logs</button>
</div>
</div>
Working with htmx#
The entry script calls initComponents(document) once the page loads, and again with
each newly inserted element on the htmx:after:process event. Each initialiser only touches
elements it hasn't seen yet (it marks them with data-init), so a fragment's
components come alive without anything running twice.
import "htmx.org";
import { initComponents } from "htmx-ui";
document.addEventListener("DOMContentLoaded", () => initComponents(document));
document.addEventListener("htmx:after:process", (e) => initComponents(e.target ?? document));
htmx:after:swap is dispatched on the element that made the request, so its target is not the new content.
htmx:after:process fires on each element htmx inserts, which is exactly what needs initialising.
Upgrading#
Before 1.0 a minor release can rename a class or change a component's markup, and every one of them is listed
under Breaking changes in the changelog. You don't make those edits by
hand: htmx-ui-upgrade rewrites your templates, scripts, stylesheets and config to the release you
are moving to, then prints the places it left for you to check. It refuses to run on uncommitted changes, so
an upgrade is one diff to review — or to undo. See Upgrading.
What it's built on#
- htmx 4 for requests and swaps.
- Tailwind CSS v4 for styling and tokens.
- Bun for bundling, the dev server and tests.
- Nunjucks macros (optional) for less repetitive markup.