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

    Engine

    htmx-ui-engine builds a site from Nunjucks pages: file-based routing, Tailwind, a dev server with hot reload and mock htmx endpoints, and static builds, on Bun or Node.

    View .md

    htmx-ui-engine is the build tool behind npm create htmx-ui and this website. It turns a folder of HTML pages into a static site: each page is rendered with Nunjucks, then bundled (scripts and styles hashed and minified, Tailwind compiled from every class the page uses). The same engine runs on two runtimes:

    RuntimeDev serverBuildTailwind
    BunBun.serve with HMRBun.buildbun-plugin-tailwind
    NodeVite with HMRVite@tailwindcss/vite

    Both render pages with the same code, so a site builds the same way on either.

    Commands#

    package.json
    {
      "scripts": {
        "dev": "htmx-ui dev",
        "build": "htmx-ui build",
        "preview": "htmx-ui preview"
      }
    }
    Command / optionDescription
    htmx-ui devDev server at http://localhost:3000 with hot reload: scripts and styles update in place; pages reload when a layout, partial, macro or data file changes.
    htmx-ui buildBuilds every page into dist/ (same paths as pages/), with scripts, styles and images in dist/assets/, content-hashed and shared by every page that uses them. Copies public/ next to it.
    htmx-ui previewServes dist/ with the same clean URLs (/about for about.html).
    --port <n>Port for dev and preview. $PORT works too.
    --root <dir>Project root, if not the current directory.
    --bun, --nodePick the runtime (also HTMX_UI_RUNTIME=bun|node).

    The runtime follows whatever started the command: bun run dev and bunx htmx-ui run on Bun; npm run, pnpm, yarn and npx run on Node. Install the Tailwind integration for the one you use:

    bun add -d htmx-ui-engine tailwindcss bun-plugin-tailwind

    Project layout#

    my-site/
    pages/index.html          -> /
    pages/about.html          -> /about
    pages/docs/index.html     -> /docs
    pages/docs/setup.html     -> /docs/setup
    layouts/  partials/       shared templates
    data/*.json               read from templates with json()
    public/                   served at / and copied to dist/ as-is
    app.ts  styles.css        client entry and stylesheet
    htmx-ui.config.ts         optional

    Pages, layouts and partials are covered in Templates & data. With htmx-ui installed, its macros and icons resolve by name, after your own files: {% from "components/icon/icon.html" import icon %}.

    Link files in public/ with a root-absolute URL (href="/favicon.svg"); they keep their names. Link everything else with asset(), so the bundler can hash it.

    Configuration#

    Every option is optional, and a project without htmx-ui.config.ts uses the defaults.

    htmx-ui.config.ts
    import { defineConfig } from "htmx-ui-engine";
    
    export default defineConfig({
      pages: "pages",
      templates: ["."],
      outDir: "dist",
      publicDir: "public",
      url: "https://example.com",
      port: 3000,
    });
    OptionDescription
    pagesRoutes directory. Default "pages".
    templatesTemplate roots, searched in order. Default ["."]; htmx-ui's src/ is added last (ui: false turns that off).
    outDirBuild output. Default "dist".
    publicDirCopied as-is. Default "public"; false to disable.
    urlPublic site URL, the origin template global. $SITE_URL overrides it.
    portDev and preview port. Default 3000; $PORT overrides it.
    globals, filtersExtra Nunjucks globals and filters.
    transform(html, page)Post-process every rendered page, in dev and build.
    routesDev-only request handlers (see below).
    fetch(req)Dev-only fallback for anything else; return null for a 404.
    build.minify, build.sourcemap, build.defineProduction bundling options.
    build.done(ctx)Runs after a build with { outDir, pages }, e.g. to write a sitemap.
    viteNode only: extra Vite config, merged over the engine's.

    Mock htmx endpoints#

    While you build the front end, routes answers hx-get and hx-post with HTML fragments, so no backend is needed. Handlers take a web Request and return a Response, with Bun.serve's route syntax, on either runtime:

    htmx-ui.config.ts
    import { defineConfig } from "htmx-ui-engine";
    
    const html = (body: string) => new Response(body, { headers: { "Content-Type": "text/html" } });
    
    export default defineConfig({
      routes: {
        "/api/users/:id": (req) => html(`<p>User ${req.params.id}</p>`),
        "/api/search": async (req) => {
          const q = new URL(req.url).searchParams.get("q") ?? "";
          return html(`<li>Results for ${q}</li>`);
        },
      },
    });
    Dev server only
    The build is static, so these routes don't exist in production. Point the same URLs at your real backend there.

    Use the plugins directly#

    Already have a build script or a Vite config? Use the engine's plugin and skip the CLI:

    import { htmxUi } from "htmx-ui-engine/bun";
    import tailwind from "bun-plugin-tailwind";
    
    await Bun.build({
      entrypoints: ["./pages/index.html", "./pages/about.html"],
      root: "./pages",
      outdir: "./dist",
      plugins: [await htmxUi(), tailwind], // htmx-ui first, so Tailwind sees rendered markup
    });

    The package root, htmx-ui-engine, exports the runtime-independent core for your own tooling: defineConfig, loadConfig, render, renderPage, findPages, routeFor and highlight.