HTMX UI
Esc

    Type to search pages and sections.

    You're viewing the docs for v0.1.
    The latest version is next. Go to this page in next.

    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.

    View .md

    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 --primary and --radius, exposed to Tailwind as bg-primary, text-muted-foreground and 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

    Ready

    Built from main in 42 seconds.

    html
    <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.

    app.ts
    import "htmx.org";
    import { initComponents } from "htmx-ui";
    
    document.addEventListener("DOMContentLoaded", () => initComponents(document));
    document.addEventListener("htmx:after:process", (e) => initComponents(e.target ?? document));
    Why not htmx:after:swap?
    In htmx 4, 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.

    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.