{
  "name": "HTMX UI",
  "description": "Tailwind CSS components for htmx apps. Plain HTML, themeable with CSS variables, dark mode built in.",
  "url": "https://susonwaiba.github.io/htmx-ui",
  "version": "0.1",
  "pages": [
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1.md",
      "title": "Introduction",
      "description": "HTMX UI is a set of Tailwind CSS components for apps where the server renders HTML and htmx swaps it into the page.",
      "section": "Getting started",
      "headings": [
        {
          "level": 2,
          "id": "what",
          "text": "What it is"
        },
        {
          "level": 2,
          "id": "example",
          "text": "A quick look"
        },
        {
          "level": 2,
          "id": "htmx",
          "text": "Working with htmx"
        },
        {
          "level": 2,
          "id": "stack",
          "text": "What it's built on"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "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."
        },
        {
          "id": "what",
          "title": "What it is",
          "text": "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."
        },
        {
          "id": "example",
          "title": "A quick look",
          "text": "Components compose like plain HTML. This card uses the card, badge and button components together: 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>"
        },
        {
          "id": "htmx",
          "title": "Working with htmx",
          "text": "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."
        },
        {
          "id": "stack",
          "title": "What it's built on",
          "text": "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."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/installation",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/installation",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/installation.md",
      "title": "Installation",
      "description": "Start a new HTMX UI site with one command, or add the components to an app that already uses Tailwind CSS v4 and htmx.",
      "section": "Getting started",
      "headings": [
        {
          "level": 2,
          "id": "packages",
          "text": "Packages"
        },
        {
          "level": 2,
          "id": "requirements",
          "text": "Requirements"
        },
        {
          "level": 2,
          "id": "new-project",
          "text": "New project"
        },
        {
          "level": 2,
          "id": "engine-by-hand",
          "text": "Add the engine to a project"
        },
        {
          "level": 3,
          "id": "1-install",
          "text": "1. Install"
        },
        {
          "level": 3,
          "id": "2-add-the-scripts",
          "text": "2. Add the scripts"
        },
        {
          "level": 3,
          "id": "3-import-the-styles",
          "text": "3. Import the styles"
        },
        {
          "level": 3,
          "id": "4-initialise-components",
          "text": "4. Initialise components"
        },
        {
          "level": 3,
          "id": "5-add-a-layout-and-a-page",
          "text": "5. Add a layout and a page"
        },
        {
          "level": 2,
          "id": "existing-project",
          "text": "Existing project"
        },
        {
          "level": 3,
          "id": "already-using-the-dark-variant",
          "text": "Already using the dark: variant?"
        },
        {
          "level": 2,
          "id": "exports",
          "text": "What the package exports"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "Commands are shown for bun, npm, pnpm and yarn. Pick yours once; every command block on the site remembers the choice."
        },
        {
          "id": "packages",
          "title": "Packages",
          "text": "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."
        },
        {
          "id": "requirements",
          "title": "Requirements",
          "text": "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."
        },
        {
          "id": "new-project",
          "title": "New project",
          "text": "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."
        },
        {
          "id": "engine-by-hand",
          "title": "Add the engine to a project",
          "text": ""
        },
        {
          "id": "1-install",
          "title": "1. Install",
          "text": "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: Terminal 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"
        },
        {
          "id": "2-add-the-scripts",
          "title": "2. Add the scripts",
          "text": "package.json { \"scripts\": { \"dev\": \"htmx-ui dev\", \"build\": \"htmx-ui build\", \"preview\": \"htmx-ui preview\" } }"
        },
        {
          "id": "3-import-the-styles",
          "title": "3. Import the styles",
          "text": "Import Tailwind first, then the library. The @source line tells Tailwind where your own markup lives. styles.css @import \"tailwindcss\"; @import \"htmx-ui/styles.css\"; @source \"./\";"
        },
        {
          "id": "4-initialise-components",
          "title": "4. Initialise components",
          "text": "app.ts 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); });"
        },
        {
          "id": "5-add-a-layout-and-a-page",
          "title": "5. Add a layout and a page",
          "text": "asset() makes the script path relative to whichever page is rendering, so the bundler finds it from any depth. layouts/base.html <!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> pages/index.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"
        },
        {
          "id": "existing-project",
          "title": "Existing project",
          "text": "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 on htmx:after:process, as in step 4 above. Clashing variable names The library defines CSS variables such as --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)."
        },
        {
          "id": "already-using-the-dark-variant",
          "title": "Already using the dark: variant?",
          "text": "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."
        },
        {
          "id": "exports",
          "title": "What the package exports",
          "text": "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 %}."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/engine",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/engine",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/engine.md",
      "title": "Engine",
      "description": "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.",
      "section": "Getting started",
      "headings": [
        {
          "level": 2,
          "id": "cli",
          "text": "Commands"
        },
        {
          "level": 2,
          "id": "layout",
          "text": "Project layout"
        },
        {
          "level": 2,
          "id": "config",
          "text": "Configuration"
        },
        {
          "level": 2,
          "id": "mock-endpoints",
          "text": "Mock htmx endpoints"
        },
        {
          "level": 2,
          "id": "plugins",
          "text": "Use the plugins directly"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "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: Runtime Dev server Build Tailwind Bun Bun.serve with HMR Bun.build bun-plugin-tailwind Node Vite with HMR Vite @tailwindcss/vite Both render pages with the same code, so a site builds the same way on either."
        },
        {
          "id": "cli",
          "title": "Commands",
          "text": "package.json { \"scripts\": { \"dev\": \"htmx-ui dev\", \"build\": \"htmx-ui build\", \"preview\": \"htmx-ui preview\" } } Command / option Description htmx-ui dev Dev 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 build Builds 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 preview Serves 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, --node Pick 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 npm install -D htmx-ui-engine tailwindcss vite @tailwindcss/vite"
        },
        {
          "id": "layout",
          "title": "Project layout",
          "text": "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."
        },
        {
          "id": "config",
          "title": "Configuration",
          "text": "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, }); Option Description pages Routes directory. Default \"pages\". templates Template roots, searched in order. Default [\".\"]; htmx-ui's src/ is added last (ui: false turns that off). outDir Build output. Default \"dist\". publicDir Copied as-is. Default \"public\"; false to disable. url Public site URL, the origin template global. $SITE_URL overrides it. port Dev and preview port. Default 3000; $PORT overrides it. globals, filters Extra Nunjucks globals and filters. transform(html, page) Post-process every rendered page, in dev and build. routes Dev-only request handlers (see below). fetch(req) Dev-only fallback for anything else; return null for a 404. build.minify, build.sourcemap, build.define Production bundling options. build.done(ctx) Runs after a build with { outDir, pages }, e.g. to write a sitemap. vite Node only: extra Vite config, merged over the engine's."
        },
        {
          "id": "mock-endpoints",
          "title": "Mock htmx endpoints",
          "text": "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."
        },
        {
          "id": "plugins",
          "title": "Use the plugins directly",
          "text": "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 }); import { htmxUi } from \"htmx-ui-engine/vite\"; import tailwindcss from \"@tailwindcss/vite\"; export default { plugins: [htmxUi(), tailwindcss()], }; The package root, htmx-ui-engine, exports the runtime-independent core for your own tooling: defineConfig, loadConfig, render, renderPage, findPages, routeFor and highlight."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/theming",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/theming",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/theming.md",
      "title": "Theming",
      "description": "Every colour and corner in HTMX UI comes from a CSS variable. Override the variables to restyle the whole library; scope them to restyle part of a page.",
      "section": "Customization",
      "headings": [
        {
          "level": 2,
          "id": "how-it-works",
          "text": "How it works"
        },
        {
          "level": 2,
          "id": "tokens",
          "text": "Tokens"
        },
        {
          "level": 2,
          "id": "override",
          "text": "Overriding tokens"
        },
        {
          "level": 2,
          "id": "custom-themes",
          "text": "Custom themes"
        },
        {
          "level": 2,
          "id": "scrollbars",
          "text": "Scrollbars"
        },
        {
          "level": 2,
          "id": "new-tokens",
          "text": "Adding your own tokens"
        },
        {
          "level": 2,
          "id": "components",
          "text": "Customising components"
        },
        {
          "level": 2,
          "id": "in-your-markup",
          "text": "Using tokens in your own markup"
        }
      ],
      "sections": [
        {
          "id": "how-it-works",
          "title": "How it works",
          "text": "Theming happens in two layers, both in htmx-ui/styles.css: Variables. Plain custom properties on :root hold the actual values, with a second set under :root.dark. Tailwind colours. A @theme inline block maps each variable to a Tailwind colour, so --primary becomes bg-primary, text-primary, border-primary/30 and so on. htmx-ui/styles.css (excerpt) :root { --radius: 0.5rem; --primary: var(--color-indigo-600); --primary-foreground: var(--color-white); /* … */ } :root.dark { --primary: var(--color-indigo-500); /* … */ } @theme inline { --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); /* … */ } Because the mapping is inline, the generated utilities read the variable at runtime: .bg-primary compiles to background-color: var(--primary). Change the variable anywhere (globally, under .dark, or on one element) and everything using it updates. No rebuild needed."
        },
        {
          "id": "tokens",
          "title": "Tokens",
          "text": "Components only use these tokens. Swatches show the current theme; toggle dark mode in the header to compare. Variable Utility Used for Light Dark --background bg-background Page background white zinc-950 --foreground text-foreground Default text zinc-950 zinc-50 --surface bg-surface Cards, alerts, raised panels white zinc-900 --muted bg-muted Subtle fills: code, table heads zinc-100 zinc-800 --muted-foreground text-muted-foreground Secondary text zinc-500 zinc-400 --accent bg-accent Hover and selected backgrounds zinc-100 zinc-800 --accent-foreground text-accent-foreground Text on accent zinc-900 zinc-50 --border border-border Borders and dividers (the default border colour) zinc-200 zinc-800 --ring outline-ring Focus rings indigo-500 indigo-400 --primary bg-primary Brand colour: primary buttons, links indigo-600 indigo-500 --primary-foreground text-primary-foreground Text on primary white white --secondary bg-secondary Secondary buttons and badges zinc-100 zinc-800 --secondary-foreground text-secondary-foreground Text on secondary zinc-900 zinc-50 --info text-info Info alerts and badges sky-600 sky-400 --success text-success Success alerts and badges emerald-600 emerald-400 --warning text-warning Warning alerts and badges amber-600 amber-400 --danger bg-danger Destructive buttons, error alerts red-600 red-500 --danger-foreground text-danger-foreground Text on danger white white --scrollbar-thumb (scrollbars) Scrollbar thumb; every scrollbar is thin with a transparent track foreground at 16% foreground at 16% --scrollbar-thumb-hover (scrollbars) Scrollbar thumb while hovering the scroll area foreground at 32% foreground at 32% --radius rounded-(--radius) Corner radius for buttons, inputs, alerts; cards add 4px 0.5rem 0.5rem Values are Tailwind palette colours, for example var(--color-indigo-600). Any CSS colour works: hex, rgb(), oklch()."
        },
        {
          "id": "override",
          "title": "Overriding tokens",
          "text": "Redefine variables after importing the library. Set both the light and dark values for anything that should differ between them. src/styles.css @import \"tailwindcss\"; @import \"htmx-ui/styles.css\"; :root { --primary: oklch(0.6 0.2 30); /* coral */ --primary-foreground: white; --radius: 0.75rem; } :root.dark { --primary: oklch(0.7 0.17 30); } Selector specificity The library declares its tokens on :root and :root.dark inside @layer base. Unlayered CSS beats layered CSS, so overrides written outside any layer (as above) always win, whatever the import order."
        },
        {
          "id": "custom-themes",
          "title": "Custom themes",
          "text": "A theme is just a selector that redefines tokens. Scope one to an attribute and you can apply it to a whole page or a single section. These previews use the same markup, with a different data-theme on each wrapper: default Pro Same markup, different tokens. Upgrade Later rose Pro Same markup, different tokens. Upgrade Later emerald Pro Same markup, different tokens. Upgrade Later src/styles.css [data-theme=\"rose\"] { --primary: var(--color-rose-600); --ring: var(--color-rose-500); --radius: 1rem; } .dark [data-theme=\"rose\"] { --primary: var(--color-rose-500); } html <!-- Whole page <!-- Whole page --> <html data-theme=\"rose\"> <!-- Or one section <!-- Or one section --> <section data-theme=\"rose\">…</section>"
        },
        {
          "id": "scrollbars",
          "title": "Scrollbars",
          "text": "The library styles every scrollbar to match: thin, with a transparent track and a thumb tinted from --foreground, so it blends into any theme and follows dark mode. Browsers without scrollbar-color (Safari) get the same look through ::-webkit-scrollbar. src/styles.css :root { --scrollbar-thumb: color-mix(in oklab, var(--primary) 30%, transparent); } /* Native scrollbars for one element */ .native-scrollbars { scrollbar-color: auto; scrollbar-width: auto; }"
        },
        {
          "id": "new-tokens",
          "title": "Adding your own tokens",
          "text": "Follow the same two steps to add a colour that Tailwind utilities can use: src/styles.css :root { --brand-gradient-from: var(--color-fuchsia-500); } :root.dark { --brand-gradient-from: var(--color-fuchsia-400); } @theme inline { --color-brand-from: var(--brand-gradient-from); } Now from-brand-from, text-brand-from and the rest are available, and they follow dark mode."
        },
        {
          "id": "components",
          "title": "Customising components",
          "text": "Component classes live in @layer components, which sits below utilities. You can restyle them two ways: One element: add utilities. <button class=\"btn btn-primary rounded-full px-8\"> gets a pill shape because utilities win. Everywhere: extend the class in your own @layer components block. src/styles.css @layer components { .btn { @apply font-semibold tracking-tight; } .card { @apply shadow-md; } }"
        },
        {
          "id": "in-your-markup",
          "title": "Using tokens in your own markup",
          "text": "Use the token utilities instead of raw palette colours, and your own UI themes and switches modes with the library: html <aside class=\"rounded-(--radius) border border-border bg-surface p-4 text-muted-foreground\"> Follows the theme and dark mode automatically. </aside>"
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/dark-mode",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/dark-mode",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/dark-mode.md",
      "title": "Dark mode",
      "description": "Light and dark are two sets of token values, switched by a .dark class on <html>. The theme switcher follows the OS until the user picks, and never flashes the wrong theme.",
      "section": "Customization",
      "headings": [
        {
          "level": 2,
          "id": "how-it-works",
          "text": "How it works"
        },
        {
          "level": 2,
          "id": "switcher",
          "text": "Theme switcher"
        },
        {
          "level": 3,
          "id": "following-the-os",
          "text": "Following the OS"
        },
        {
          "level": 2,
          "id": "no-flash",
          "text": "Avoiding a flash of the wrong theme"
        },
        {
          "level": 2,
          "id": "dark-variant",
          "text": "Writing dark styles"
        },
        {
          "level": 2,
          "id": "force",
          "text": "Forcing a theme"
        },
        {
          "level": 2,
          "id": "custom-switcher",
          "text": "Building your own switcher"
        }
      ],
      "sections": [
        {
          "id": "how-it-works",
          "title": "How it works",
          "text": "A class, not a media query. Tailwind v4's dark: variant normally follows prefers-color-scheme, which a button can't change. The library redefines it to match a .dark class instead. Tokens swap. :root.dark redefines every colour variable, so components don't need dark: classes at all. Native widgets follow. color-scheme switches too, so scrollbars and form controls match. htmx-ui/styles.css (excerpt) @custom-variant dark (&:where(.dark, .dark *)); @layer base { :root { color-scheme: light; --background: var(--color-white); /* … */ } :root.dark { color-scheme: dark; --background: var(--color-zinc-950); /* … */ } }"
        },
        {
          "id": "switcher",
          "title": "Theme switcher",
          "text": "Any element with data-theme-toggle becomes a toggle. Try this one; it's the same toggle as the one in the header. html <button type=\"button\" class=\"btn btn-outline\" data-theme-toggle aria-pressed=\"false\"> <span class=\"dark:hidden\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41\"/></svg></span> <span class=\"hidden dark:inline\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"/></svg></span> Theme: <span data-theme-label>light</span> </button> When clicked, initTheme() from htmx-ui/theme: flips the .dark class on <html>, saves the choice to localStorage under the key theme, sets aria-pressed=\"true\" on every toggle while dark is active, and writes the current theme (light or dark) into any [data-theme-label] inside a toggle. src/app.ts import { initTheme } from \"htmx-ui/theme\"; document.addEventListener(\"DOMContentLoaded\", () => initTheme());"
        },
        {
          "id": "following-the-os",
          "title": "Following the OS",
          "text": "Until the user clicks a toggle, the theme follows prefers-color-scheme and updates live when the OS changes. After a click, the stored choice wins. To go back to following the OS, clear it: ts localStorage.removeItem(\"theme\");"
        },
        {
          "id": "no-flash",
          "title": "Avoiding a flash of the wrong theme",
          "text": "Module scripts run after the first paint, so on a dark-mode visit the page would briefly render light. Put this tiny inline script in <head>, before your stylesheet and app script, so the class is set before anything is drawn: <head> <script> (() => { const stored = localStorage.getItem(\"theme\"); const dark = stored ? stored === \"dark\" : matchMedia(\"(prefers-color-scheme: dark)\").matches; document.documentElement.classList.toggle(\"dark\", dark); })(); </script> Keep it in sync This script repeats the logic in initTheme(). If you change the storage key or the class name, change both."
        },
        {
          "id": "dark-variant",
          "title": "Writing dark styles",
          "text": "Prefer tokens: bg-surface and text-muted-foreground already adapt. For one-off tweaks, use the dark: variant as usual: html <div class=\"rounded-(--radius) bg-amber-100 px-4 py-3 text-sm text-amber-900 dark:bg-amber-400/10 dark:text-amber-300\"> Amber in light, a soft glow in dark. </div>"
        },
        {
          "id": "force",
          "title": "Forcing a theme",
          "text": "To keep part of the page dark (a code sample, a hero) whatever the global theme, add the dark class to that element. Everything inside picks up the dark variant. Note that :root.dark tokens only apply at the root, so set the tokens you need on the element too: css .force-dark { --background: var(--color-zinc-950); --foreground: var(--color-zinc-50); --border: var(--color-zinc-800); color-scheme: dark; } html <section class=\"dark force-dark bg-background text-foreground\">…</section>"
        },
        {
          "id": "custom-switcher",
          "title": "Building your own switcher",
          "text": "getTheme() returns the active theme. A light/dark/system menu, for example, only needs to set or remove the stored value and toggle the class: ts import { getTheme } from \"htmx-ui/theme\"; function setTheme(choice: \"light\" | \"dark\" | \"system\") { if (choice === \"system\") localStorage.removeItem(\"theme\"); else localStorage.setItem(\"theme\", choice); const dark = choice === \"system\" ? matchMedia(\"(prefers-color-scheme: dark)\").matches : choice === \"dark\"; document.documentElement.classList.toggle(\"dark\", dark); } console.log(getTheme()); // \"light\" | \"dark\""
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/templates",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/templates",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/templates.md",
      "title": "Templates & data",
      "description": "Pages are Nunjucks templates rendered at build time. They can read JSON files, inline SVGs and list files, so most content needs no API server.",
      "section": "Guides",
      "headings": [
        {
          "level": 2,
          "id": "pages",
          "text": "Pages and layouts"
        },
        {
          "level": 2,
          "id": "json",
          "text": "JSON data"
        },
        {
          "level": 3,
          "id": "json-in-the-browser-too",
          "text": "JSON in the browser too"
        },
        {
          "level": 2,
          "id": "svg",
          "text": "SVG and icons"
        },
        {
          "level": 2,
          "id": "glob",
          "text": "Listing files"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "Optional The components are plain HTML and CSS, and work with any server or template language. This page covers the Nunjucks templates of htmx-ui-engine, which this site is built with too. Macros are new The package's Nunjucks macros (htmx-ui/components/*.html) need the engine's template helpers (svg(), json(), | dedent, | highlight). Their parameters may still change in a minor release before 1.0; each change is listed in the changelog."
        },
        {
          "id": "pages",
          "title": "Pages and layouts",
          "text": "Every .html file in pages/ is a route (docs/theming.html is /docs/theming). The engine renders it with Nunjucks before the bundler (Bun's or Vite) sees it, so pages can extend layouts, include partials and call macros. Template names resolve from the project root, then from the htmx-ui package (its components/ and icons/): pages/pricing.html {% extends \"layouts/site.html\" %} {% from \"components/alert/alert.html\" import alert %} {% block title %}Pricing · HTMX UI{% endblock %} {% block content %} {% call alert(\"Launch offer\", variant=\"success\") %}20% off the first year.{% endcall %} {% endblock %}"
        },
        {
          "id": "json",
          "title": "JSON data",
          "text": "json(path) reads a JSON file at build time. Keep repeated content (navigation, pricing tiers, team members, release notes) in one file and render it anywhere, with no API and no client-side fetching. {% set releases = json(\"data/changelog.json\").releases %} <ul> {% for r in releases %} <li><a href=\"/docs/changelog/{{ r.version }}\">v{{ r.version }}</a>, {{ r.date }}</li> {% endfor %} </ul> { \"releases\": [ { \"version\": \"0.1.0\", \"date\": \"2026-10-02\", \"summary\": \"First release…\" } ] } That template, live, reading this site's real file: v0.1.0, 2026-10-02 This site's data files: File (under site/) Description data/site.json Site name, description and URL (used for sitemaps). data/docs-nav.json Docs navigation: sidebar, prev/next, components index, page order for agents. data/changelog.json Releases, newest first. data/versions.json Docs versions; the latest is rendered live, older ones are archived."
        },
        {
          "id": "json-in-the-browser-too",
          "title": "JSON in the browser too",
          "text": "The same files can be imported by client code. Bun and Vite bundle them as plain objects: ts import nav from \"./data/docs-nav.json\"; console.log(nav.sections.length);"
        },
        {
          "id": "svg",
          "title": "SVG and icons",
          "text": "svg(path, attrs) inlines an SVG file and sets attributes on its root, and the icon macro builds on it. Inlined SVG inherits colour and costs no extra requests. jinja {{ svg(\"icons/sun.svg\", { class: \"size-5 text-primary\", \"aria-hidden\": \"true\" }) }}"
        },
        {
          "id": "glob",
          "title": "Listing files",
          "text": "glob(pattern) returns matching paths across the template roots, sorted. The icon gallery is generated this way, from the package's icons/: jinja {% for path in glob(\"icons/*.svg\") %}…{% endfor %}"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Function / filter Description json(path) Parsed JSON file. Paths are relative to a template root (the project, then htmx-ui); first match wins. svg(path, attrs) Inline SVG with attributes set on the root <svg>. glob(pattern) Sorted list of root-relative paths matching the pattern. asset(path) Root-relative path rewritten relative to the current page (for <script src>, <link href>). url The current page's route, e.g. /docs/templates. origin The public site URL: url in htmx-ui.config.ts, or $SITE_URL. | dedent Remove shared indentation. | highlight(lang) Syntax-highlight a string at build time. All of them work inside imported macros too, without with context."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/ai-agents",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/ai-agents",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/ai-agents.md",
      "title": "AI agents",
      "description": "Every docs page is published as Markdown, with an llms.txt index and JSON sitemaps, so coding agents can read the docs as specs.",
      "section": "Guides",
      "headings": [
        {
          "level": 2,
          "id": "markdown",
          "text": "Markdown pages"
        },
        {
          "level": 2,
          "id": "indexes",
          "text": "Indexes"
        },
        {
          "level": 2,
          "id": "prompts",
          "text": "Prompts"
        },
        {
          "level": 3,
          "id": "prompt-llms-txt",
          "text": "Use the docs for one task"
        },
        {
          "level": 3,
          "id": "prompt-specific-pages",
          "text": "Point at specific pages"
        },
        {
          "level": 2,
          "id": "skill",
          "text": "Make it a skill"
        },
        {
          "level": 2,
          "id": "how",
          "text": "How it's generated"
        }
      ],
      "sections": [
        {
          "id": "markdown",
          "title": "Markdown pages",
          "text": "Add .md to any docs URL to get the page as Markdown: /docs/components/button.md, /docs.md. Each file starts with frontmatter (title, description, URL, section). Live examples become their source code, and reference tables become Markdown tables. The Copy Markdown button at the top of every page copies it for pasting into a chat. /docs/components/badge.md --- title: \"Badge\" description: \"A small label for status, counts or categories.\" url: \"/docs/components/badge\" section: \"Components\" --- # Badge A small label for status, counts or categories. ```html <span class=\"badge badge-primary\">New</span> ```"
        },
        {
          "id": "indexes",
          "title": "Indexes",
          "text": "Each opens in a new tab. URL Contents /llms.txt llms.txt index (llmstxt.org): every docs page as a Markdown link with its description, grouped by section. Start here. /llms-full.txt Every docs page's Markdown in one file, for loading the whole library into context. /sitemap.json Every page with URL, Markdown URL, title, description, section, heading outline and section text, plus the list of docs versions. The index for site search. /docs/sitemap.json The same for the latest docs only. Each older version has its own, e.g. /docs/v0.1/sitemap.json. /docs/versions.json Every docs version with its URL prefix, its sitemap and the pages it contains. /sitemap.xml Standard XML sitemap for search engines. /robots.txt Allows crawling and points at the sitemap. /sitemap.json (excerpt) { \"name\": \"HTMX UI\", \"url\": \"https://example.com\", \"version\": \"0.1\", \"versions\": [ { \"id\": \"0.1\", \"label\": \"v0.1\", \"path\": \"/docs\", \"latest\": true, \"sitemap\": \"/docs/sitemap.json\" } ], \"pages\": [ { \"url\": \"/docs/components/tabs\", \"markdown\": \"/docs/components/tabs.md\", \"title\": \"Tabs\", \"section\": \"Components\", \"headings\": [{ \"level\": 2, \"id\": \"markup\", \"text\": \"Markup\" }], \"sections\": [{ \"id\": \"markup\", \"title\": \"Markup\", \"text\": \"Each [data-tab] button shows the [data-tab-panel]…\" }] } ] } Every sections[].id is a heading anchor, so url + \"#\" + id opens that exact section. This site's own search (Ctrl K) works this way, and an MCP server can list pages from these files and link straight to the right place."
        },
        {
          "id": "prompts",
          "title": "Prompts",
          "text": "These work with any agent that can fetch URLs (Claude, ChatGPT, Cursor, Copilot and others). Copy, then replace the task."
        },
        {
          "id": "prompt-llms-txt",
          "title": "Use the docs for one task",
          "text": "Prompt Read https://susonwaiba.github.io/htmx-ui/llms.txt. It indexes the htmx-ui docs; every link is a Markdown page. Fetch the pages relevant to the task below and follow their markup and class names exactly. Use htmx-ui's design tokens (bg-primary, text-muted-foreground, border-border) instead of raw colours. Task: build a settings page with tabs for Profile and Billing, and a danger-zone card with a delete button."
        },
        {
          "id": "prompt-specific-pages",
          "title": "Point at specific pages",
          "text": "Prompt Build a settings page using htmx-ui. Follow these specs exactly: https://susonwaiba.github.io/htmx-ui/docs/components/card.md https://susonwaiba.github.io/htmx-ui/docs/components/tabs.md"
        },
        {
          "id": "skill",
          "title": "Make it a skill",
          "text": "Save the lookup steps as a skill so your agent uses the docs automatically, every time you work on UI, without pasting URLs again. Ask your agent to create it: Prompt Create a skill named \"htmx-ui\" for this project (for Claude Code: .claude/skills/htmx-ui/SKILL.md). It should trigger whenever I build or change UI with htmx-ui components, classes, theming or dark mode. The skill must tell you to: 1. Fetch https://susonwaiba.github.io/htmx-ui/llms.txt to find the relevant docs pages. 2. Fetch those pages as Markdown (https://susonwaiba.github.io/htmx-ui/docs/<page>.md) and follow their markup exactly. 3. Check the htmx-ui version in package.json; if it's older than the latest docs, read https://susonwaiba.github.io/htmx-ui/docs/versions.json and use that version's pages instead. 4. Prefer design tokens over raw Tailwind colours, and call initComponents() for new htmx content. Keep the skill short and link to the docs rather than copying them. Or create the file yourself: .claude/skills/htmx-ui/SKILL.md --- name: htmx-ui description: Build UI with the htmx-ui component library (Tailwind CSS v4 + htmx). Use when adding or changing buttons, cards, alerts, tabs, dropdowns, tables, prose, theming or dark mode in this project. --- # htmx-ui The docs are written for agents. Don't guess class names; look them up. 1. Fetch the index: https://susonwaiba.github.io/htmx-ui/llms.txt 2. Fetch the pages you need as Markdown, e.g. https://susonwaiba.github.io/htmx-ui/docs/components/tabs.md 3. Match the docs to the installed version: read \"htmx-ui\" in package.json. If it's older than the latest docs, use the matching version from https://susonwaiba.github.io/htmx-ui/docs/versions.json (pages live under its \"path\"). 4. Search for a topic in https://susonwaiba.github.io/htmx-ui/sitemap.json: `pages[].sections[]` hold the text of every section. Rules: - Copy markup and class names from the docs exactly. - Use design tokens (bg-primary, text-muted-foreground, border-border), not raw pale"
        },
        {
          "id": "how",
          "title": "How it's generated",
          "text": "Nothing is written by hand. At build time the site's engine config (site/htmx-ui.config.ts) renders every page, converts the article body to Markdown (site/lib/markdown.ts), and writes the indexes (site/lib/site.ts). The dev server generates the same files on request. Page order follows the docs navigation in site/data/docs-nav.json. Absolute URLs (and the URLs in the prompts above) use the SITE_URL environment variable, falling back to url in site/data/site.json. Every docs heading gets an id and a # link, so sections can be linked directly. Add data-md-skip to an element to leave it out of the Markdown and the search text (purely visual blocks). Terminal SITE_URL=https://htmx-ui.example bun run build Write for both readers Agents read exactly what people read, so a clear page works for both. Show the full markup in every example, say what each class does in the reference table, and describe behaviour in words, not just in a live demo."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components.md",
      "title": "Components",
      "description": "Every component is a set of CSS classes, plus a small behaviour where one is needed. Pick one to see live examples and the full class reference.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "anatomy",
          "text": "Anatomy of a component"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "Accordion Stacked headings that reveal sections; one or many open, no JavaScript. Alert Callouts for info, success, warnings and errors. Badge Small status labels and counters. Button Actions in every variant, size and state, including loading. Button group Join buttons, inputs and menus: split buttons, toolbars, vertical groups. Card Header, content and footer, with media, an action slot and scrolling content. Code block Highlighted source with a copy button and package-manager tabs. Dropdown A menu of links or actions that opens from a button. Field Labels, help text and messages for controls; inline, responsive and grid forms. Icon SVG icons inlined at build time, or linked as images. Input Text inputs, textareas, selects and file pickers with every state. Input group Icons, text, buttons, menus and spinners inside an input's border. Popover A floating panel of rich content that opens from a button. Sidebar Responsive navigation column with off-canvas mode. Spinner Loading indicator from any icon or pure CSS; works in buttons, badges and inputs. Table Readable data tables with optional hover rows. Tabs Switch between panels; optionally synced and remembered. Text Long-form content with .prose (Tailwind Typography), plus helpers for single elements. Toggle A two-state button, on or off, in any button style. Toggle group A set of toggle buttons: pick one, at most one, or many."
        },
        {
          "id": "anatomy",
          "title": "Anatomy of a component",
          "text": "Each component has its own directory, htmx-ui/components/<name>/ (packages/ui/src/components/<name>/ in the repository), holding: <name>.css: classes in @layer components, built from token utilities with @apply. <name>.ts (optional): an init<Name>(root) function that wires up [data-<name>] elements once each, with <name>.test.ts beside it. <name>.html (optional): a Nunjucks macro for markup that is tedious to write by hand."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/accordion",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/accordion",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/accordion.md",
      "title": "Accordion",
      "description": "A vertically stacked set of headings that each reveal a section of content. Built on <details>, so it works without JavaScript.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "markup",
          "text": "Markup"
        },
        {
          "level": 2,
          "id": "single",
          "text": "One open at a time"
        },
        {
          "level": 2,
          "id": "multiple",
          "text": "Multiple open"
        },
        {
          "level": 2,
          "id": "disabled",
          "text": "Disabled"
        },
        {
          "level": 2,
          "id": "borders",
          "text": "Borders"
        },
        {
          "level": 2,
          "id": "htmx",
          "text": "Loading content with htmx"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <div class=\"accordion max-w-lg\"> <details class=\"accordion-item\" name=\"faq-basic\" open> <summary class=\"accordion-trigger\">Is it accessible?</summary> <div class=\"accordion-content\"> <p>Yes. It's a native <code>&lt;details&gt;</code> element, so keyboards and screen readers handle it out of the box.</p> </div> </details> <details class=\"accordion-item\" name=\"faq-basic\"> <summary class=\"accordion-trigger\">Does it need JavaScript?</summary> <div class=\"accordion-content\"> <p>No. Opening, closing and \"one at a time\" are all done by the browser. An optional behaviour fully locks disabled items.</p> </div> </details> <details class=\"accordion-item\" name=\"faq-basic\"> <summary class=\"accordion-trigger\">Can it load content from the server?</summary> <div class=\"accordion-content\"> <p>Yes, with htmx. See the example further down.</p> </div> </details> </div>"
        },
        {
          "id": "markup",
          "title": "Markup",
          "text": "Each item is a <details class=\"accordion-item\"> with a <summary class=\"accordion-trigger\"> heading and a .accordion-content body. Wrap the items in .accordion. Add open to an item to show it expanded on load. On older browsers that can't animate to height: auto, items open instantly instead of sliding."
        },
        {
          "id": "single",
          "title": "One open at a time",
          "text": "Give every item the same name. Opening one closes the others, as in the example above. Use a different name for each accordion on the page."
        },
        {
          "id": "multiple",
          "title": "Multiple open",
          "text": "Leave name out, and each item opens and closes on its own. html <div class=\"accordion max-w-lg\"> <details class=\"accordion-item\" open> <summary class=\"accordion-trigger\">Shipping</summary> <div class=\"accordion-content\"><p>Orders ship within two business days.</p></div> </details> <details class=\"accordion-item\" open> <summary class=\"accordion-trigger\">Returns</summary> <div class=\"accordion-content\"><p>Return anything within 30 days for a full refund.</p></div> </details> <details class=\"accordion-item\"> <summary class=\"accordion-trigger\">Warranty</summary> <div class=\"accordion-content\"><p>Two years on every product, parts and labour.</p></div> </details> </div>"
        },
        {
          "id": "disabled",
          "title": "Disabled",
          "text": "Add aria-disabled=\"true\" and tabindex=\"-1\" to the summary: it fades, ignores pointer clicks and drops out of the tab order. Screen readers and scripts can still activate a <summary>, so also put data-accordion on the wrapper. Its one-line behaviour stops a disabled summary from opening its item. html <div class=\"accordion max-w-lg\" data-accordion> <details class=\"accordion-item\" name=\"faq-disabled\"> <summary class=\"accordion-trigger\">Available</summary> <div class=\"accordion-content\"><p>This one opens.</p></div> </details> <details class=\"accordion-item\" name=\"faq-disabled\"> <summary class=\"accordion-trigger\" aria-disabled=\"true\" tabindex=\"-1\">Locked until you upgrade</summary> <div class=\"accordion-content\"><p>Hidden content.</p></div> </details> <details class=\"accordion-item\" name=\"faq-disabled\"> <summary class=\"accordion-trigger\">Also available</summary> <div class=\"accordion-content\"><p>So does this one.</p></div> </details> </div>"
        },
        {
          "id": "borders",
          "title": "Borders",
          "text": ".accordion-bordered draws a box around the accordion. .accordion-separated gives each item its own box. html <div class=\"accordion accordion-bordered max-w-lg\"> <details class=\"accordion-item\" name=\"faq-bordered\" open> <summary class=\"accordion-trigger\">Billing</summary> <div class=\"accordion-content\"><p>Invoices go out on the first of each month.</p></div> </details> <details class=\"accordion-item\" name=\"faq-bordered\"> <summary class=\"accordion-trigger\">Security</summary> <div class=\"accordion-content\"><p>All data is encrypted at rest and in transit.</p></div> </details> <details class=\"accordion-item\" name=\"faq-bordered\"> <summary class=\"accordion-trigger\">Integrations</summary> <div class=\"accordion-content\"><p>Connect over webhooks or the REST API.</p></div> </details> </div> <div class=\"accordion accordion-separated max-w-lg\"> <details class=\"accordion-item\" name=\"faq-separated\"> <summary class=\"accordion-trigger\">Separated item</summary> <div class=\"accordion-content\"><p>Each item is its own box.</p></div> </details> <details class=\"accordion-item\" name=\"faq-separated\"> <summary class=\"accordion-trigger\">Another item</summary> <div class=\"accordion-content\"><p>With space between them.</p></div> </details> </div>"
        },
        {
          "id": "htmx",
          "title": "Loading content with htmx",
          "text": "<details> fires a toggle event when it opens. hx-trigger=\"toggle once\" fetches the body the first time the item is opened. html <div class=\"accordion max-w-lg\"> <details class=\"accordion-item\" hx-get=\"/api/hello\" hx-trigger=\"toggle once\" hx-target=\"find .accordion-content\"> <summary class=\"accordion-trigger\">Server message</summary> <div class=\"accordion-content\"><p>Loading…</p></div> </details> </div>"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .accordion Wrapper around the items. Add data-accordion when any item is disabled. .accordion-item On <details>. Same name on several items: only one open at a time. .accordion-trigger On <summary>. Draws the chevron. aria-disabled=\"true\" + tabindex=\"-1\" disables it. .accordion-content The revealed body. .accordion-bordered Box around the whole accordion, with padded rows. .accordion-separated Each item in its own box."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/alert",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/alert",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/alert.md",
      "title": "Alert",
      "description": "A callout for information the reader should notice: tips, confirmations, warnings and errors.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "variants",
          "text": "Variants with icons"
        },
        {
          "level": 2,
          "id": "dismissible",
          "text": "Dismissible"
        },
        {
          "level": 2,
          "id": "macro",
          "text": "Nunjucks macro"
        },
        {
          "level": 2,
          "id": "accessibility",
          "text": "Accessibility"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <div class=\"alert\" role=\"status\"> <div class=\"alert-title\">Heads up</div> <div class=\"alert-description\">You can add components to your app using the CLI.</div> </div>"
        },
        {
          "id": "variants",
          "title": "Variants with icons",
          "text": "Put an <svg> first and the text lines up beside it. Variant classes tint the border, background, icon and title. html <div class=\"alert alert-info\" role=\"status\"> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4M12 8h.01\"/></svg> <div class=\"alert-title\">New version available</div> <div class=\"alert-description\">v0.1.0 adds dark mode and five new components.</div> </div> <div class=\"alert alert-success\" role=\"status\"> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m9 12 2 2 4-4\"/></svg> <div class=\"alert-title\">Payment received</div> <div class=\"alert-description\">A receipt is on its way to your inbox.</div> </div> <div class=\"alert alert-warning\" role=\"status\"> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4M12 17h.01\"/></svg> <div class=\"alert-title\">Your trial ends in 3 days</div> <div class=\"alert-description\">Add a payment method to keep your projects online.</div> </div> <div class=\"alert alert-danger\" role=\"alert\"> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m15 9-6 6M9 9l6 6\"/></svg> <div class=\"alert-title\">Deploy failed</div> <div class=\"alert-description\">The build e"
        },
        {
          "id": "dismissible",
          "title": "Dismissible",
          "text": "Add data-dismissible to the alert and data-dismiss to a button inside it. Clicking the button removes the alert. This also works for alerts that arrive in htmx fragments. html <div class=\"alert alert-info\" role=\"status\" data-dismissible> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4M12 8h.01\"/></svg> <div class=\"alert-title\">Cookies</div> <div class=\"alert-description\">We only use essential cookies.</div> <button type=\"button\" class=\"alert-close\" data-dismiss aria-label=\"Dismiss\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg></button> </div>"
        },
        {
          "id": "macro",
          "title": "Nunjucks macro",
          "text": "If your templates use Nunjucks, the alert macro picks the icon and ARIA role for you: page.html {% from \"components/alert/alert.html\" import alert %} {% call alert(\"Deploy failed\", variant=\"danger\", dismissible=true) %} The build exited with code 1. {% endcall %}"
        },
        {
          "id": "accessibility",
          "title": "Accessibility",
          "text": "Use role=\"alert\" for urgent, time-sensitive messages (screen readers announce it right away), and role=\"status\" for everything else. Give icon-only close buttons an aria-label."
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .alert Container. A leading <svg> becomes the icon column. .alert-title Bold first line. .alert-description Body text in the muted colour. .alert-info / -success / -warning / -danger Status tint for border, background, icon and title. .alert-close Top-right close button. Pair with data-dismiss. [data-dismissible] + [data-dismiss] Behaviour: clicking [data-dismiss] removes the [data-dismissible] element."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/badge",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/badge",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/badge.md",
      "title": "Badge",
      "description": "A small label for status, counts or categories.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "variants",
          "text": "Variants"
        },
        {
          "level": 2,
          "id": "status",
          "text": "Status"
        },
        {
          "level": 2,
          "id": "dot",
          "text": "With a dot or icon"
        },
        {
          "level": 2,
          "id": "in-context",
          "text": "In context"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <span class=\"badge badge-primary\">New</span>"
        },
        {
          "id": "variants",
          "title": "Variants",
          "text": "html <span class=\"badge badge-primary\">Primary</span> <span class=\"badge badge-secondary\">Secondary</span> <span class=\"badge badge-outline\">Outline</span>"
        },
        {
          "id": "status",
          "title": "Status",
          "text": "Soft, tinted variants for status. They use the --info, --success, --warning and --danger tokens. html <span class=\"badge badge-info\">Info</span> <span class=\"badge badge-success\">Success</span> <span class=\"badge badge-warning\">Warning</span> <span class=\"badge badge-danger\">Danger</span>"
        },
        {
          "id": "dot",
          "title": "With a dot or icon",
          "text": "html <span class=\"badge badge-success badge-dot\">Operational</span> <span class=\"badge badge-warning badge-dot\">Degraded</span> <span class=\"badge badge-outline\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M20 6 9 17l-5-5\"/></svg> Verified</span>"
        },
        {
          "id": "in-context",
          "title": "In context",
          "text": "html <button class=\"btn btn-outline\">Inbox <span class=\"badge badge-primary\">12</span></button> <h3 class=\"h4 flex items-center gap-2\">Billing <span class=\"badge badge-secondary\">Beta</span></h3>"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .badge Base class. .badge-primary / -secondary / -outline Neutral and brand styles. .badge-info / -success / -warning / -danger Soft status colours. .badge-dot Adds a leading dot in the badge's text colour."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/button",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/button",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/button.md",
      "title": "Button",
      "description": "Triggers an action or an htmx request. Works on <button>, <a> and <input type=\"submit\">.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "variants",
          "text": "Variants"
        },
        {
          "level": 2,
          "id": "sizes",
          "text": "Sizes"
        },
        {
          "level": 2,
          "id": "icon",
          "text": "Icon"
        },
        {
          "level": 2,
          "id": "with-icon",
          "text": "With icon"
        },
        {
          "level": 2,
          "id": "rounded",
          "text": "Rounded"
        },
        {
          "level": 2,
          "id": "links",
          "text": "As a link"
        },
        {
          "level": 2,
          "id": "disabled",
          "text": "Disabled"
        },
        {
          "level": 2,
          "id": "loading",
          "text": "Loading"
        },
        {
          "level": 3,
          "id": "loading-htmx",
          "text": "While an htmx request runs"
        },
        {
          "level": 2,
          "id": "more",
          "text": "Groups and toggles"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <button class=\"btn btn-primary\">Save changes</button>"
        },
        {
          "id": "variants",
          "title": "Variants",
          "text": "Combine .btn with one variant class. html <button class=\"btn btn-primary\">Primary</button> <button class=\"btn btn-secondary\">Secondary</button> <button class=\"btn btn-outline\">Outline</button> <button class=\"btn btn-ghost\">Ghost</button> <button class=\"btn btn-danger\">Danger</button> <button class=\"btn btn-link\">Link</button>"
        },
        {
          "id": "sizes",
          "title": "Sizes",
          "text": "html <button class=\"btn btn-outline btn-xs\">Extra small</button> <button class=\"btn btn-outline btn-sm\">Small</button> <button class=\"btn btn-outline\">Default</button> <button class=\"btn btn-outline btn-lg\">Large</button>"
        },
        {
          "id": "icon",
          "title": "Icon",
          "text": ".btn-icon makes a square button for a single icon. It combines with every size. Give it an aria-label, since there's no visible text. html <button class=\"btn btn-outline btn-icon btn-xs\" aria-label=\"Add\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5v14\"/></svg></button> <button class=\"btn btn-outline btn-icon btn-sm\" aria-label=\"Add\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5v14\"/></svg></button> <button class=\"btn btn-outline btn-icon\" aria-label=\"Add\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5v14\"/></svg></button> <button class=\"btn btn-outline btn-icon btn-lg\" aria-label=\"Add\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5v14\"/></svg></button> <button class=\"btn btn-ghost btn-icon\" aria-label=\"Delete\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/></svg></button>"
        },
        {
          "id": "with-icon",
          "title": "With icon",
          "text": "Inline SVGs next to the text are sized to match the button (16px, 12px for .btn-xs). html <button class=\"btn btn-primary\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3\"/></svg> Download</button> <button class=\"btn btn-outline\">Next <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5l7 7-7 7\"/></svg></button> <button class=\"btn btn-secondary btn-sm\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><rect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\"/><path d=\"m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7\"/></svg> Email</button> <button class=\"btn btn-ghost btn-xs\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5v14\"/></svg> Add row</button>"
        },
        {
          "id": "rounded",
          "title": "Rounded",
          "text": ".btn-rounded gives fully rounded ends; an icon button becomes a circle. html <button class=\"btn btn-primary btn-rounded\">Get started</button> <button class=\"btn btn-outline btn-rounded\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z\"/></svg> Star</button> <button class=\"btn btn-secondary btn-icon btn-rounded\" aria-label=\"Add\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5v14\"/></svg></button>"
        },
        {
          "id": "links",
          "title": "As a link",
          "text": "html <a href=\"/docs/installation\" class=\"btn btn-secondary\">Read the guide</a>"
        },
        {
          "id": "disabled",
          "title": "Disabled",
          "text": "Use the disabled attribute on buttons, or aria-disabled=\"true\" on links. html <button class=\"btn btn-primary\" disabled>Disabled</button> <a class=\"btn btn-outline\" aria-disabled=\"true\">Disabled link</a>"
        },
        {
          "id": "loading",
          "title": "Loading",
          "text": "Put a spinner in the button. aria-busy=\"true\" blocks clicks while keeping full colour; disabled blocks them and fades the button. html <button class=\"btn btn-primary\" aria-busy=\"true\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> Saving</button> <button class=\"btn btn-outline\" disabled><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> Please wait</button> <button class=\"btn btn-secondary btn-icon\" aria-busy=\"true\" aria-label=\"Loading\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg></button>"
        },
        {
          "id": "loading-htmx",
          "title": "While an htmx request runs",
          "text": "Give the spinner .htmx-indicator. htmx adds .htmx-request to the button during the request, which reveals it; until then it takes no space. hx-disable=\"this\" prevents double submits. html <button class=\"btn btn-primary\" hx-post=\"/api/slow\" hx-target=\"next .result\" hx-disable=\"this\"> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner htmx-indicator\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> Save </button> <span class=\"result muted\" aria-live=\"polite\"></span> The /api/slow endpoint is a mock that only exists under bun run dev."
        },
        {
          "id": "more",
          "title": "Groups and toggles",
          "text": "Join buttons with a button group, make one switch on and off with toggle, or pick between options with a toggle group."
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .btn Base class. Required on every button. .btn-primary Solid brand colour, for the main action. .btn-secondary Muted fill, for secondary actions. .btn-outline Bordered, transparent background. .btn-ghost No border or fill until hovered. .btn-danger Destructive actions. .btn-link Looks like a text link, keeps button semantics. .btn-xs / .btn-sm / .btn-lg 24px, 32px or 44px tall instead of the default 36px. .btn-icon Square button for a single icon. Combine with a size. .btn-rounded Fully rounded ends; a circle with .btn-icon. aria-busy=\"true\" Loading: ignores clicks without fading."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/button-group",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/button-group",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/button-group.md",
      "title": "Button group",
      "description": "A container that joins related buttons, inputs and menus into one unit with shared borders.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "accessibility",
          "text": "Accessibility"
        },
        {
          "level": 2,
          "id": "orientation",
          "text": "Orientation"
        },
        {
          "level": 2,
          "id": "size",
          "text": "Size"
        },
        {
          "level": 2,
          "id": "icons",
          "text": "Icons"
        },
        {
          "level": 2,
          "id": "separator",
          "text": "Separator"
        },
        {
          "level": 2,
          "id": "nested",
          "text": "Nested"
        },
        {
          "level": 2,
          "id": "input",
          "text": "With an input"
        },
        {
          "level": 3,
          "id": "input-group",
          "text": "With an input group"
        },
        {
          "level": 2,
          "id": "split",
          "text": "Split button"
        },
        {
          "level": 2,
          "id": "popover",
          "text": "Popover"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <div class=\"btn-group\" role=\"group\" aria-label=\"Message actions\"> <button class=\"btn btn-outline\">Archive</button> <button class=\"btn btn-outline\">Report</button> <button class=\"btn btn-outline\">Snooze</button> </div> Use a button group for buttons that perform actions. For buttons that switch a state on and off (bold, italic, text alignment), use a toggle group."
        },
        {
          "id": "accessibility",
          "title": "Accessibility",
          "text": "Give the container role=\"group\" and an aria-label so assistive technology announces the buttons as a set. For a group of groups, use role=\"toolbar\" on the outer one."
        },
        {
          "id": "orientation",
          "title": "Orientation",
          "text": "Add .btn-group-vertical to stack the buttons. html <div class=\"btn-group btn-group-vertical\" role=\"group\" aria-label=\"Zoom\"> <button class=\"btn btn-outline btn-icon\" aria-label=\"Zoom in\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5v14\"/></svg></button> <button class=\"btn btn-outline btn-icon\" aria-label=\"Zoom out\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14\"/></svg></button> </div> <div class=\"btn-group btn-group-vertical\" role=\"group\" aria-label=\"Views\"> <button class=\"btn btn-outline\">Day</button> <button class=\"btn btn-outline\">Week</button> <button class=\"btn btn-outline\">Month</button> </div>"
        },
        {
          "id": "size",
          "title": "Size",
          "text": ".btn-group-sm and .btn-group-lg resize every button, input and text in the group, so you don't size them one by one. html <div class=\"btn-group btn-group-sm\" role=\"group\" aria-label=\"Small\"> <button class=\"btn btn-outline\">Small</button> <button class=\"btn btn-outline\">Group</button> <button class=\"btn btn-outline btn-icon\" aria-label=\"Add\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5v14\"/></svg></button> </div> <div class=\"btn-group\" role=\"group\" aria-label=\"Default\"> <button class=\"btn btn-outline\">Default</button> <button class=\"btn btn-outline\">Group</button> <button class=\"btn btn-outline btn-icon\" aria-label=\"Add\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5v14\"/></svg></button> </div> <div class=\"btn-group btn-group-lg\" role=\"group\" aria-label=\"Large\"> <button class=\"btn btn-outline\">Large</button> <button class=\"btn btn-outline\">Group</button> <button class=\"btn btn-outline btn-icon\" aria-label=\"Add\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5v14\"/></svg></button> </div>"
        },
        {
          "id": "icons",
          "title": "Icons",
          "text": "html <div class=\"btn-group\" role=\"group\" aria-label=\"Post\"> <button class=\"btn btn-outline\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z\"/></svg> Star</button> <button class=\"btn btn-outline\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z\"/></svg> Save</button> <button class=\"btn btn-outline btn-icon\" aria-label=\"Copy link\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/></svg></button> </div>"
        },
        {
          "id": "separator",
          "title": "Separator",
          "text": "Buttons without a border of their own (secondary, primary) run into each other. Put a .btn-group-separator between them. html <div class=\"btn-group\" role=\"group\" aria-label=\"Clipboard\"> <button class=\"btn btn-secondary\">Copy</button> <div class=\"btn-group-separator\" role=\"separator\"></div> <button class=\"btn btn-secondary\">Paste</button> </div> <div class=\"btn-group btn-group-vertical\" role=\"group\" aria-label=\"Order\"> <button class=\"btn btn-secondary btn-icon\" aria-label=\"Move up\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"m18 15-6-6-6 6\"/></svg></button> <div class=\"btn-group-separator\" role=\"separator\"></div> <button class=\"btn btn-secondary btn-icon\" aria-label=\"Move down\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\"/></svg></button> </div>"
        },
        {
          "id": "nested",
          "title": "Nested",
          "text": "Groups inside a group are spaced apart instead of joined: a toolbar. html <div class=\"btn-group\" role=\"toolbar\" aria-label=\"Pagination\"> <div class=\"btn-group\" role=\"group\" aria-label=\"Pages\"> <button class=\"btn btn-outline btn-sm\">1</button> <button class=\"btn btn-outline btn-sm\">2</button> <button class=\"btn btn-outline btn-sm\">3</button> </div> <div class=\"btn-group\" role=\"group\" aria-label=\"Step\"> <button class=\"btn btn-outline btn-sm btn-icon\" aria-label=\"Previous\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M19 12H5M12 19l-7-7 7-7\"/></svg></button> <button class=\"btn btn-outline btn-sm btn-icon\" aria-label=\"Next\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5l7 7-7 7\"/></svg></button> </div> </div>"
        },
        {
          "id": "input",
          "title": "With an input",
          "text": "Inputs and selects join the group and fill the space left by the buttons. html <div class=\"btn-group w-full max-w-sm\" role=\"group\" aria-label=\"Search\"> <input class=\"input\" type=\"search\" placeholder=\"Search…\" aria-label=\"Search\" /> <button class=\"btn btn-outline btn-icon\" aria-label=\"Search\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/></svg></button> </div> <div class=\"btn-group w-full max-w-sm\" role=\"group\" aria-label=\"Website\"> <span class=\"btn-group-text\">https://</span> <input class=\"input\" placeholder=\"example.com\" aria-label=\"Website\" /> <select class=\"select w-auto flex-none\" aria-label=\"Domain\"> <option>.com</option> <option>.org</option> </select> </div>"
        },
        {
          "id": "input-group",
          "title": "With an input group",
          "text": "An input group can sit in a button group too, with addons of its own. html <div class=\"btn-group w-full max-w-md\" role=\"group\" aria-label=\"Message\"> <button class=\"btn btn-outline btn-icon\" aria-label=\"Attach\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5v14\"/></svg></button> <div class=\"input-group\"> <input class=\"input\" placeholder=\"Send a message…\" aria-label=\"Message\" /> <span class=\"input-group-addon input-group-addon-inline-end\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" role=\"status\" aria-label=\"Loading\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg></span> </div> <button class=\"btn btn-primary btn-icon\" aria-label=\"Send\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z\"/><path d=\"m21.854 2.147-10.94 10.939\"/></svg></button> </div>"
        },
        {
          "id": "split",
          "title": "Split button",
          "text": "A main action plus a dropdown of related ones. The .dropdown wrapper joins the group like a button. html <div class=\"btn-group\" role=\"group\" aria-label=\"Merge\"> <button class=\"btn btn-primary\">Merge pull request</button> <div class=\"btn-group-separator\" role=\"separator\"></div> <div class=\"dropdown\" data-dropdown> <button class=\"btn btn-primary btn-icon\" data-dropdown-trigger aria-haspopup=\"menu\" aria-expanded=\"false\" aria-label=\"More merge options\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\"/></svg></button> <div class=\"dropdown-menu dropdown-menu-end\" role=\"menu\" hidden> <button class=\"dropdown-item\" role=\"menuitem\">Squash and merge</button> <button class=\"dropdown-item\" role=\"menuitem\">Rebase and merge</button> </div> </div> </div> <div class=\"btn-group\" role=\"group\" aria-label=\"Follow\"> <button class=\"btn btn-outline\">Follow</button> <div class=\"dropdown\" data-dropdown> <button class=\"btn btn-outline btn-icon\" data-dropdown-trigger aria-haspopup=\"menu\" aria-expanded=\"false\" aria-label=\"More\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/></svg></button> <div class=\"dropdown-menu dropdown-menu-end\" role=\"menu\" hidden> <button class=\"dropdown-item\" role=\"menuitem\">Mute conversation</button> <button class=\"dropdown-item\" role=\"menuitem\">Copy link</button> <div class=\"dropdown-separator\" role=\"separator\"></div> <button class=\"dropdown-item text-danger\" role=\"menuitem\">Report</button> </div> </div> </div>"
        },
        {
          "id": "popover",
          "title": "Popover",
          "text": "A popover trigger works the same way, for content that isn't a menu. html <div class=\"btn-group\" role=\"group\" aria-label=\"Copilot\"> <button class=\"btn btn-outline\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M12 8V4H8\"/><rect width=\"16\" height=\"12\" x=\"4\" y=\"8\" rx=\"2\"/><path d=\"M2 14h2M20 14h2M15 13v2M9 13v2\"/></svg> Copilot</button> <div class=\"popover\" data-popover> <button class=\"btn btn-outline btn-icon\" data-popover-trigger aria-expanded=\"false\" aria-label=\"Open popover\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\"/></svg></button> <div class=\"popover-content popover-content-end\" data-popover-content hidden> <p class=\"popover-title\">Start a new task</p> <p class=\"popover-description\">Describe what you want done.</p> <textarea class=\"textarea mt-3\" placeholder=\"Fix the flaky test in…\" aria-label=\"Task\" autofocus></textarea> </div> </div> </div>"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .btn-group Joins its children: inner corners squared, shared borders overlapped. Groups inside it are spaced instead. .btn-group-vertical Stacks the children. .btn-group-sm / -lg Resizes every button, input and text in the group. .btn-group-text Static label styled to sit in a group, like https://. .btn-group-separator 1px divider between buttons that have no border."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/card",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/card",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/card.md",
      "title": "Card",
      "description": "Displays a card with a header, content and footer, plus optional media, a header action and scrolling content.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "parts",
          "text": "Parts"
        },
        {
          "level": 2,
          "id": "image",
          "text": "Image"
        },
        {
          "level": 3,
          "id": "horizontal",
          "text": "Horizontal"
        },
        {
          "level": 2,
          "id": "scroll",
          "text": "Scrolling content"
        },
        {
          "level": 2,
          "id": "size",
          "text": "Size"
        },
        {
          "level": 2,
          "id": "link",
          "text": "Clickable card"
        },
        {
          "level": 2,
          "id": "htmx",
          "text": "Loading content with htmx"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <div class=\"card w-full max-w-sm\"> <div class=\"card-header\"> <h3 class=\"card-title\">Log in to your account</h3> <p class=\"card-description\">Enter your email below to log in.</p> <div class=\"card-action\"><a href=\"#signup\" class=\"btn btn-link\">Sign up</a></div> </div> <div class=\"card-content\"> <div class=\"field\"> <label class=\"field-label\" for=\"card-email\">Email</label> <input class=\"input\" id=\"card-email\" type=\"email\" placeholder=\"you@example.com\" /> </div> </div> <div class=\"card-footer flex-col\"> <button class=\"btn btn-primary w-full\">Log in</button> <button class=\"btn btn-outline w-full\">Log in with SSO</button> </div> </div>"
        },
        {
          "id": "parts",
          "title": "Parts",
          "text": "Every part is optional. .card itself has no padding; the header, content and footer each pad themselves, so media, tables and lists can run edge to edge. .card-header holds .card-title and .card-description. .card-action, inside the header, sits in its top-right corner beside the title and description. .card-content is the body. .card-footer is a row for actions, pinned to the bottom of tall cards. html <div class=\"card w-full max-w-sm\"> <div class=\"card-header\"> <h3 class=\"card-title\">Notifications</h3> <p class=\"card-description\">You have 3 unread messages.</p> <div class=\"card-action\"> <button class=\"btn btn-ghost btn-icon btn-sm\" aria-label=\"Settings\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/></svg></button> </div> </div> <div class=\"card-content\"> <p class=\"muted\">Your call has been confirmed. Your subscription is expiring soon.</p> </div> <div class=\"card-footer justify-end\"> <button class=\"btn btn-ghost\">Dismiss</button> <button class=\"btn btn-primary\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M20 6 9 17l-5-5\"/></svg> Mark all as read</button> </div> </div>"
        },
        {
          "id": "image",
          "title": "Image",
          "text": ".card-image on an <img> (or video, or any media block) runs it edge to edge. As the first or last child, its outer corners follow the card's. html <div class=\"card w-full max-w-xs\"> <img class=\"card-image aspect-video\" src=\"../../../images/landscape.svg\" alt=\"Mountains at dusk\" /> <div class=\"card-header pt-5\"> <h3 class=\"card-title\">Weekend in the mountains</h3> <p class=\"card-description\">Two nights, guided hikes, all meals.</p> <div class=\"card-action\"><span class=\"badge badge-secondary\">New</span></div> </div> <div class=\"card-footer pt-5\"> <button class=\"btn btn-primary w-full\">Book now</button> </div> </div> <div class=\"card w-full max-w-xs\"> <div class=\"card-header pb-5\"> <h3 class=\"card-title\">Image at the bottom</h3> <p class=\"card-description\">Last child, so the bottom corners round.</p> </div> <img class=\"card-image aspect-video\" src=\"../../../images/landscape.svg\" alt=\"Mountains at dusk\" /> </div>"
        },
        {
          "id": "horizontal",
          "title": "Horizontal",
          "text": ".card-horizontal puts the media beside the text from the sm breakpoint. Wrap the other parts in .card-body. html <div class=\"card card-horizontal w-full max-w-lg\"> <img class=\"card-image aspect-video sm:aspect-auto\" src=\"../../../images/landscape.svg\" alt=\"Mountains at dusk\" /> <div class=\"card-body\"> <div class=\"card-header\"> <h3 class=\"card-title\">Alpine route</h3> <p class=\"card-description\">12 km, 800 m of climbing.</p> </div> <div class=\"card-content\"><p class=\"muted\">A full-day loop past three lakes. Start early.</p></div> <div class=\"card-footer\"><button class=\"btn btn-outline btn-sm\">View map</button></div> </div> </div>"
        },
        {
          "id": "scroll",
          "title": "Scrolling content",
          "text": "Add .card-scroll to .card-content to give long content its own scrollbar while the header and footer stay put. Limit the height on the card (h-80, max-h-96…) or on the content (max-h-48). html <div class=\"card h-80 w-full max-w-sm\"> <div class=\"card-header\"> <h3 class=\"card-title\">Terms of service</h3> <p class=\"card-description\">Last updated October 2026.</p> </div> <div class=\"card-content card-scroll space-y-3 text-sm text-muted-foreground\"> <p>By using the service you agree to these terms. Please read them carefully.</p> <p>You are responsible for the content you publish and for keeping your account secure.</p> <p>We may change the service at any time. We'll tell you about significant changes in advance.</p> <p>We can suspend accounts that break these terms, after warning you where we reasonably can.</p> <p>The service is provided as is, without warranties of any kind.</p> <p>These terms are governed by the laws of the place where we are registered.</p> </div> <div class=\"card-footer border-t pt-4 justify-end\"> <button class=\"btn btn-ghost\">Decline</button> <button class=\"btn btn-primary\">Accept</button> </div> </div>"
        },
        {
          "id": "size",
          "title": "Size",
          "text": ".card-sm uses tighter padding. html <div class=\"card card-sm w-full max-w-xs\"> <div class=\"card-header\"> <h3 class=\"card-title\">Small card</h3> <p class=\"card-description\">16px padding instead of 24px.</p> </div> <div class=\"card-content\"><p class=\"muted text-sm\">Good for dense dashboards.</p></div> </div>"
        },
        {
          "id": "link",
          "title": "Clickable card",
          "text": "Make the whole card an <a> and add .card-link for a hover state. html <a href=\"/docs/components\" class=\"card card-link w-full max-w-sm\"> <div class=\"card-header pb-6\"> <h3 class=\"card-title flex items-center justify-between\">All components <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5l7 7-7 7\"/></svg></h3> <p class=\"card-description\">Browse every component in the library.</p> </div> </a>"
        },
        {
          "id": "htmx",
          "title": "Loading content with htmx",
          "text": "Cards make good swap targets. This one fetches its body from the server: html <div class=\"card w-full max-w-sm\"> <div class=\"card-header\"> <h3 class=\"card-title\">Server message</h3> <p class=\"card-description\">Fetched with hx-get.</p> </div> <div class=\"card-content\" id=\"card-body\"> <button class=\"btn btn-outline btn-sm\" hx-get=\"/api/hello\" hx-target=\"#card-body\">Load</button> </div> </div>"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .card Container: border, surface colour, radius and shadow. .card-header Top section, padded on three sides. .card-title Heading text. .card-description Muted supporting text. .card-action Inside the header: top-right slot beside the title and description. .card-content Main body, padded on all sides. .card-scroll On .card-content: scrolls on its own when the card or content has a height limit. .card-footer Bottom row for actions; pinned to the bottom of tall cards. .card-image Edge-to-edge media; outer corners follow the card's. .card-horizontal Media beside the content from the sm breakpoint. Wrap the parts in .card-body. .card-sm Tighter padding. .card-link Hover style for cards that are links."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/code-block",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/code-block",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/code-block.md",
      "title": "Code block",
      "description": "Syntax-highlighted source with a copy button, file tabs, and package-manager tabs that remember the reader's choice. Every snippet in these docs uses it.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "highlighting",
          "text": "Syntax highlighting"
        },
        {
          "level": 2,
          "id": "package-managers",
          "text": "Package-manager commands"
        },
        {
          "level": 2,
          "id": "file-tabs",
          "text": "File tabs"
        },
        {
          "level": 2,
          "id": "markup",
          "text": "Markup"
        },
        {
          "level": 2,
          "id": "macros",
          "text": "Nunjucks macros"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "src/app.ts import { initComponents } from \"htmx-ui\"; // Wire up components inside content htmx inserts document.addEventListener(\"htmx:after:process\", (e) => { initComponents((e.target as ParentNode) ?? document); });"
        },
        {
          "id": "highlighting",
          "title": "Syntax highlighting",
          "text": "Code is highlighted at build time by Shiki, so pages ship coloured HTML and no highlighting JavaScript. Supported languages: html, css, ts/js, json, bash, toml, md, xml/svg and jinja (Nunjucks). Anything else is shown escaped, uncoloured. Colours are CSS variables, --code-token-keyword, --code-token-string and so on, defined for light and dark next to the other tokens. Override them to restyle every code block. src/styles.css :root { --code-token-keyword: var(--color-fuchsia-600); --code-token-string: var(--color-teal-700); }"
        },
        {
          "id": "package-managers",
          "title": "Package-manager commands",
          "text": "Install commands come in bun, npm, pnpm and yarn flavours. Switch tabs here and every command block on every page follows. The choice is saved in localStorage and restored on your next visit. bun add htmx-ui htmx.org bun add -d tailwindcss npm install htmx-ui htmx.org npm install -D tailwindcss pnpm add htmx-ui htmx.org pnpm add -D tailwindcss yarn add htmx-ui htmx.org yarn add -D tailwindcss With the Nunjucks macro, write each line with an action and it's translated for each manager: page.html {% from \"components/code/code.html\" import cli %} {{ cli(\"add htmx-ui htmx.org add-dev tailwindcss run dev\") }} Action Description init npm init -y · pnpm init · yarn init -y · bun init -y install npm install · pnpm install · yarn install · bun install add npm install · pnpm add · yarn add · bun add add-dev npm install -D · pnpm add -D · yarn add -D · bun add -d run npm run · pnpm run · yarn run · bun run exec npx · pnpm dlx · yarn dlx · bunx create npm create · pnpm create · yarn create · bun create anything else Shown unchanged on every tab, e.g. mkdir my-app && cd my-app The commands live in htmx-ui/components/code/package-managers.json; add a manager or change the default there."
        },
        {
          "id": "file-tabs",
          "title": "File tabs",
          "text": "Show related files side by side: <button class=\"btn btn-primary\" hx-post=\"/save\">Save</button> @layer components { .btn { @apply font-semibold; } }"
        },
        {
          "id": "markup",
          "title": "Markup",
          "text": "Add data-code to the block and data-copy to a button inside it. Clicking the button copies the text of the visible <code>. Inside the button, .code-copy-idle shows normally and .code-copied shows for 1.5 seconds after copying. html <div class=\"code-block\" data-code> <div class=\"code-block-header\"> <span class=\"font-mono\">app.ts</span> <button type=\"button\" class=\"code-copy\" data-copy> <span class=\"code-copy-idle\">Copy</span><span class=\"code-copied\">Copied</span> </button> </div> <pre><code>initComponents(document);</code></pre> </div> For tabs, put data-tabs on the block (plus data-tabs-sync=\"key\" to sync), .code-tab buttons with data-tab in the header, and one <pre data-tab-panel> per tab. See Tabs."
        },
        {
          "id": "macros",
          "title": "Nunjucks macros",
          "text": "page.html {% from \"components/code/code.html\" import code, code_block, code_tabs, cli %} {% call code(\"html\", title=\"index.html\") %} <button class=\"btn btn-primary\">Save</button> {% endcall %} {{ code_block(some_string, \"ts\") }} {{ code_tabs([{ id: \"a\", label: \"a.ts\", lang: \"ts\", source: a }], sync=\"files\") }} Bodies are de-indented and escaped. Wrap Nunjucks syntax in {% raw %} so it's shown, not run."
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .code-block Container: border, code background and the <pre> inside. .code-block-header Top bar for a file name, tabs and the copy button. .code-copy Small copy button. Children .code-copy-idle / .code-copied swap after copying. .code-tabs / .code-tab Tab strip inside the header. [data-code] + [data-copy] Behaviour: copies the visible <code> text. --code-background / --code-foreground / --code-token-* Highlighting colours (CSS variables)."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/dropdown",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/dropdown",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/dropdown.md",
      "title": "Dropdown",
      "description": "A menu of links or actions that opens from a button. Keyboard and screen-reader friendly. The docs version switcher in the sidebar is one.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "markup",
          "text": "Markup"
        },
        {
          "level": 2,
          "id": "keyboard",
          "text": "Keyboard"
        },
        {
          "level": 2,
          "id": "alignment",
          "text": "Alignment"
        },
        {
          "level": 2,
          "id": "htmx",
          "text": "Actions with htmx"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <div class=\"dropdown\" data-dropdown> <button type=\"button\" class=\"btn btn-outline\" data-dropdown-trigger aria-haspopup=\"menu\" aria-expanded=\"false\"> Options <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\"/></svg> </button> <div class=\"dropdown-menu\" role=\"menu\" hidden> <p class=\"dropdown-label\">My account</p> <a class=\"dropdown-item\" role=\"menuitem\" href=\"#profile\">Profile</a> <a class=\"dropdown-item\" role=\"menuitem\" href=\"#billing\">Billing <span class=\"badge badge-secondary\">Pro</span></a> <div class=\"dropdown-separator\" role=\"separator\"></div> <button type=\"button\" class=\"dropdown-item text-danger\" role=\"menuitem\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/></svg> Delete account</button> </div> </div>"
        },
        {
          "id": "markup",
          "title": "Markup",
          "text": "Wrap the trigger and menu in .dropdown with data-dropdown. The trigger gets data-dropdown-trigger, aria-haspopup=\"menu\" and aria-expanded=\"false\". The menu gets role=\"menu\" and starts hidden. Each entry is a link or button with role=\"menuitem\". Mark the selected entry with aria-current=\"true\", and unavailable ones with aria-disabled=\"true\"."
        },
        {
          "id": "keyboard",
          "title": "Keyboard",
          "text": "Key Action ↓ ↑ on the trigger Open on the first / last item ↓ ↑ Home End Move between items Esc Close and return focus to the trigger Tab, click outside, choose an item Close"
        },
        {
          "id": "alignment",
          "title": "Alignment",
          "text": "The menu opens below the trigger, aligned to its left edge. Add .dropdown-menu-end to align it to the right edge, or .dropdown-menu-up to open it above the trigger. html <div class=\"dropdown\" data-dropdown> <button type=\"button\" class=\"btn btn-ghost btn-icon\" data-dropdown-trigger aria-haspopup=\"menu\" aria-expanded=\"false\" aria-label=\"More\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M4 6h16M4 12h16M4 18h16\"/></svg></button> <div class=\"dropdown-menu dropdown-menu-end\" role=\"menu\" hidden> <a class=\"dropdown-item\" role=\"menuitem\" href=\"#edit\">Edit</a> <a class=\"dropdown-item\" role=\"menuitem\" href=\"#duplicate\">Duplicate</a> <a class=\"dropdown-item\" role=\"menuitem\" href=\"#archive\" aria-disabled=\"true\">Archive</a> </div> </div>"
        },
        {
          "id": "htmx",
          "title": "Actions with htmx",
          "text": "Menu items can make htmx requests like any other button. The menu closes when an item is chosen. html <div class=\"dropdown\" data-dropdown> <button type=\"button\" class=\"btn btn-primary\" data-dropdown-trigger aria-haspopup=\"menu\" aria-expanded=\"false\">Export <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\"/></svg></button> <div class=\"dropdown-menu\" role=\"menu\" hidden> <button type=\"button\" class=\"dropdown-item\" role=\"menuitem\" hx-post=\"/api/slow\" hx-target=\"#export-result\">CSV</button> <button type=\"button\" class=\"dropdown-item\" role=\"menuitem\" hx-post=\"/api/slow\" hx-target=\"#export-result\">JSON</button> </div> </div> <span id=\"export-result\" class=\"muted\"></span>"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .dropdown Positioning wrapper (relative, inline-block). .dropdown-menu The floating panel. Opens below, left-aligned. .dropdown-menu-end / .dropdown-menu-up Align to the right edge / open above. .dropdown-item Link or button inside the menu. aria-current and aria-disabled are styled. .dropdown-label Small muted heading inside the menu. .dropdown-separator Divider line. [data-dropdown] / [data-dropdown-trigger] Behaviour: open/close, keyboard navigation, outside click."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/field",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/field",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/field.md",
      "title": "Field",
      "description": "Labels, descriptions, help and error messages for form controls, and layouts for whole forms: stacked, inline, responsive and grid.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "anatomy",
          "text": "Anatomy"
        },
        {
          "level": 2,
          "id": "description",
          "text": "Description and help"
        },
        {
          "level": 2,
          "id": "required",
          "text": "Required"
        },
        {
          "level": 2,
          "id": "messages",
          "text": "Messages"
        },
        {
          "level": 2,
          "id": "disabled",
          "text": "Disabled"
        },
        {
          "level": 2,
          "id": "horizontal",
          "text": "Inline"
        },
        {
          "level": 3,
          "id": "choice",
          "text": "Checkboxes and radios"
        },
        {
          "level": 2,
          "id": "responsive",
          "text": "Responsive"
        },
        {
          "level": 2,
          "id": "grid",
          "text": "Grid"
        },
        {
          "level": 2,
          "id": "groups",
          "text": "With input and button groups"
        },
        {
          "level": 2,
          "id": "separator",
          "text": "Separator"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <form class=\"field-group max-w-md\" onsubmit=\"return false\"> <div class=\"field\"> <label class=\"field-label\" for=\"field-email\">Email</label> <input class=\"input\" id=\"field-email\" type=\"email\" placeholder=\"you@example.com\" required aria-describedby=\"field-email-help\" /> <p class=\"field-description\" id=\"field-email-help\">We'll only use this to send receipts.</p> </div> <div class=\"field\"> <label class=\"field-label\" for=\"field-bio\">Bio <span class=\"badge badge-outline\">Optional</span></label> <textarea class=\"textarea\" id=\"field-bio\" placeholder=\"A sentence or two about you\"></textarea> </div> <button class=\"btn btn-primary w-fit\">Save</button> </form>"
        },
        {
          "id": "anatomy",
          "title": "Anatomy",
          "text": ".field wraps one control with its .field-label and any .field-description or message, stacked. Link the description and messages to the control with aria-describedby, so screen readers read them with it. Stack fields in a .field-group, and group related ones in a <fieldset class=\"fieldset\"> with a <legend class=\"field-legend\">."
        },
        {
          "id": "description",
          "title": "Description and help",
          "text": "A .field-description can sit above or below the control. Links inside it are underlined. html <div class=\"field max-w-sm\"> <label class=\"field-label\" for=\"field-key\">API key</label> <p class=\"field-description\" id=\"field-key-help-top\">Create one in your account settings.</p> <input class=\"input\" id=\"field-key\" placeholder=\"sk_live_…\" aria-describedby=\"field-key-help-top field-key-help\" /> <p class=\"field-description\" id=\"field-key-help\">Keep it secret. <a href=\"#rotate\">How to rotate keys</a>.</p> </div>"
        },
        {
          "id": "required",
          "title": "Required",
          "text": "The label gets a red asterisk when the field holds a required control. For a control that can't carry required, add .field-label-required to the label instead. html <div class=\"field-group max-w-sm\"> <div class=\"field\"> <label class=\"field-label\" for=\"field-required\">Full name</label> <input class=\"input\" id=\"field-required\" required /> </div> <div class=\"field\"> <span class=\"field-label field-label-required\" id=\"field-plan-label\">Plan</span> <div class=\"toggle-group btn-group\" role=\"group\" aria-labelledby=\"field-plan-label\" data-toggle-group=\"single\" data-toggle-group-required> <button type=\"button\" class=\"btn btn-outline\" aria-pressed=\"true\" value=\"free\">Free</button> <button type=\"button\" class=\"btn btn-outline\" aria-pressed=\"false\" value=\"pro\">Pro</button> </div> </div> </div>"
        },
        {
          "id": "messages",
          "title": "Messages",
          "text": ".field-error, .field-warning and .field-success colour a message and size a leading icon. A field with an aria-invalid=\"true\" control turns its label red. Several errors can go in a list. html <div class=\"field max-w-sm\"> <label class=\"field-label\" for=\"field-password\">Password</label> <input class=\"input\" id=\"field-password\" type=\"password\" value=\"abc\" aria-invalid=\"true\" aria-describedby=\"field-password-error\" /> <div class=\"field-error\" id=\"field-password-error\"> <ul> <li>At least 8 characters</li> <li>At least one number</li> </ul> </div> </div>"
        },
        {
          "id": "disabled",
          "title": "Disabled",
          "text": "When the control is disabled, the label and description fade with it. html <div class=\"field max-w-sm\"> <label class=\"field-label\" for=\"field-disabled\">Workspace URL</label> <input class=\"input\" id=\"field-disabled\" value=\"acme.example.com\" disabled aria-describedby=\"field-disabled-help\" /> <p class=\"field-description\" id=\"field-disabled-help\">Contact an admin to change it.</p> </div>"
        },
        {
          "id": "horizontal",
          "title": "Inline",
          "text": ".field-horizontal puts the label beside the control. Inputs, selects and groups take the remaining width. Give labels a fixed width (w-24) to line them up. html <div class=\"field-group max-w-md\"> <div class=\"field field-horizontal\"> <label class=\"field-label w-24 shrink-0\" for=\"field-first\">First name</label> <input class=\"input\" id=\"field-first\" /> </div> <div class=\"field field-horizontal\"> <label class=\"field-label w-24 shrink-0\" for=\"field-role\">Role</label> <select class=\"select\" id=\"field-role\"><option>Developer</option><option>Designer</option></select> </div> </div>"
        },
        {
          "id": "choice",
          "title": "Checkboxes and radios",
          "text": "Put the control first. Wrap the label and a description in .field-content to stack them beside it. Native checkboxes take the brand colour with the accent-primary utility. html <fieldset class=\"fieldset max-w-md\"> <legend class=\"field-legend\">Notifications</legend> <div class=\"field field-horizontal\"> <input class=\"size-4 accent-primary\" type=\"checkbox\" id=\"field-mentions\" checked /> <div class=\"field-content\"> <label class=\"field-label\" for=\"field-mentions\">Mentions</label> <p class=\"field-description\">When someone @mentions you.</p> </div> </div> <div class=\"field field-horizontal\"> <input class=\"size-4 accent-primary\" type=\"checkbox\" id=\"field-digest\" /> <label class=\"field-label\" for=\"field-digest\">Weekly digest</label> </div> </fieldset>"
        },
        {
          "id": "responsive",
          "title": "Responsive",
          "text": ".field-responsive stacks the label above the control in a narrow container and puts them side by side once the surrounding .field-group is at least 28rem wide. It reacts to the group's width, not the viewport's, so it also works in sidebars and dialogs. Resize the window to see it. html <div class=\"field-group max-w-2xl\"> <div class=\"field field-responsive\"> <div class=\"field-content\"> <label class=\"field-label\" for=\"field-r-name\">Display name</label> <p class=\"field-description\">Shown on your profile.</p> </div> <input class=\"input\" id=\"field-r-name\" placeholder=\"Ada\" /> </div> <div class=\"field field-responsive\"> <div class=\"field-content\"> <label class=\"field-label\" for=\"field-r-email\">Email</label> <p class=\"field-description\">For sign-in and receipts.</p> </div> <input class=\"input\" id=\"field-r-email\" type=\"email\" required /> </div> </div>"
        },
        {
          "id": "grid",
          "title": "Grid",
          "text": ".field-grid lays fields out in two columns from the sm breakpoint. Span a field across both with sm:col-span-2. html <form class=\"fieldset max-w-lg\" onsubmit=\"return false\"> <legend class=\"field-legend\">Shipping address</legend> <p class=\"field-description\">Where should we send your order?</p> <div class=\"field-grid\"> <div class=\"field\"> <label class=\"field-label\" for=\"field-g-first\">First name</label> <input class=\"input\" id=\"field-g-first\" required /> </div> <div class=\"field\"> <label class=\"field-label\" for=\"field-g-last\">Last name</label> <input class=\"input\" id=\"field-g-last\" required /> </div> <div class=\"field sm:col-span-2\"> <label class=\"field-label\" for=\"field-g-street\">Street</label> <input class=\"input\" id=\"field-g-street\" /> </div> <div class=\"field\"> <label class=\"field-label\" for=\"field-g-city\">City</label> <input class=\"input\" id=\"field-g-city\" /> </div> <div class=\"field\"> <label class=\"field-label\" for=\"field-g-country\">Country</label> <select class=\"select\" id=\"field-g-country\"><option>Australia</option><option>New Zealand</option></select> </div> </div> </form>"
        },
        {
          "id": "groups",
          "title": "With input and button groups",
          "text": "html <div class=\"field-group max-w-sm\"> <div class=\"field\"> <label class=\"field-label\" for=\"field-site\">Website</label> <div class=\"input-group\"> <input class=\"input\" id=\"field-site\" placeholder=\"example.com\" aria-describedby=\"field-site-help\" /> <span class=\"input-group-addon\">https://</span> </div> <p class=\"field-description\" id=\"field-site-help\">Your public homepage.</p> </div> <div class=\"field\"> <label class=\"field-label\" for=\"field-invite\">Invite by email</label> <div class=\"btn-group w-full\" role=\"group\" aria-label=\"Invite\"> <input class=\"input\" id=\"field-invite\" type=\"email\" placeholder=\"teammate@example.com\" /> <button class=\"btn btn-outline\">Send invite</button> </div> </div> </div>"
        },
        {
          "id": "separator",
          "title": "Separator",
          "text": ".field-separator divides sections of a form, with optional text. html <div class=\"field-group max-w-sm\"> <button class=\"btn btn-outline w-full\">Continue with GitHub</button> <div class=\"field-separator\">Or continue with</div> <div class=\"field\"> <label class=\"field-label\" for=\"field-sep-email\">Email</label> <input class=\"input\" id=\"field-sep-email\" type=\"email\" /> </div> </div>"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .field One control with its label and messages, stacked. .field-horizontal Label beside the control. .field-responsive Stacked, then side by side once the .field-group is 28rem wide. .field-label Label text. Gets an asterisk when the field holds a required control. .field-label-required Forces the asterisk. .field-content Stacks a label and description beside a control in horizontal fields. .field-description Muted help text, above or below the control. .field-error / -warning / -success Coloured message with an optional leading icon or list. .field-group Vertical stack of fields; the container .field-responsive measures. .field-grid Two columns of fields from the sm breakpoint. .fieldset / .field-legend Group of related fields with a heading. .field-separator Horizontal rule with optional centred text."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/icon",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/icon",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/icon.md",
      "title": "Icon",
      "description": "SVG icons stored as files in htmx-ui/icons and inlined into the HTML at build time, so they inherit colour and need no requests. They can also be linked as images.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "usage",
          "text": "Usage"
        },
        {
          "level": 2,
          "id": "inline-vs-img",
          "text": "Inline or image"
        },
        {
          "level": 2,
          "id": "any-svg",
          "text": "Any SVG"
        },
        {
          "level": 2,
          "id": "adding",
          "text": "Adding an icon"
        },
        {
          "level": 2,
          "id": "all",
          "text": "All icons"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-6\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41\"/></svg> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-6\" aria-hidden=\"true\"><path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"/></svg> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-6 text-primary\" aria-hidden=\"true\"><path d=\"M13 2 3 14h9l-1 8 10-12h-9l1-8z\"/></svg> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-6 text-success\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m9 12 2 2 4-4\"/></svg>"
        },
        {
          "id": "usage",
          "title": "Usage",
          "text": "Icons are 24×24 Lucide-style SVGs drawn with stroke=\"currentColor\", so they take the text colour. Size them with Tailwind (size-4, size-5…). The macro inlines the file's <svg> and adds your class: page.html {% from \"components/icon/icon.html\" import icon %} {{ icon(\"sun\") }} {# decorative: aria-hidden #} {{ icon(\"info\", \"size-5\", label=\"Note\") }} {# meaningful: role=\"img\" + aria-label #} {{ icon(\"sun\", mode=\"img\") }} {# <img> pointing at a copy of the file #}"
        },
        {
          "id": "inline-vs-img",
          "title": "Inline or image",
          "text": "Inline (default) mode=\"img\" Colour Inherits text colour (text-primary works) Fixed black stroke; the macro adds dark:invert Requests None, part of the HTML One per icon; the file is content-hashed, so it caches forever Use when Almost always Many repeats of a large SVG, or linking from elsewhere html <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-6 text-primary\" aria-hidden=\"true\"><path d=\"M12 22a10 10 0 1 1 10-10c0 2.76-2.24 4-5 4h-1.5a1.5 1.5 0 0 0-1 2.6A1.5 1.5 0 0 1 12 22Z\"/><circle cx=\"7.5\" cy=\"11.5\" r=\"1\"/><circle cx=\"10.5\" cy=\"7.5\" r=\"1\"/><circle cx=\"15.5\" cy=\"8.5\" r=\"1\"/></svg> <img src=\"../../../../packages/ui/src/icons/palette.svg\" class=\"size-6 dark:invert\" width=\"24\" height=\"24\" alt=\"\" aria-hidden=\"true\" /> In img mode the bundler copies the SVG next to the page with a content hash in its name (sun-a1b2c3.svg), so a changed icon is never served stale. Separately, this site's build copies the package's icons/ to dist/assets/icons/ with unchanged names. /assets/icons/<name>.svg is therefore a stable URL for linking from other sites, emails or docs, e.g. /assets/icons/sun.svg."
        },
        {
          "id": "any-svg",
          "title": "Any SVG",
          "text": "The svg() template function inlines any SVG file under a template root (your project, then htmx-ui), setting or overriding attributes on its root element: jinja {{ svg(\"images/logo.svg\", { class: \"h-8 w-auto\", \"aria-label\": \"HTMX UI\", role: \"img\" }) }}"
        },
        {
          "id": "adding",
          "title": "Adding an icon",
          "text": "Save a 24×24 SVG with stroke=\"currentColor\" (or fill=\"currentColor\") as icons/<name>.svg in your project; your file wins over a package icon of the same name. Lucide icons drop straight in. Use it: {{ icon(\"<name>\") }}. A misspelt name fails the build."
        },
        {
          "id": "all",
          "title": "All icons",
          "text": "54 icons, listed straight from the package's icons/ with glob(): Icon names: alert-triangle, align-center, align-left, align-right, arrow-left, arrow-right, asterisk, bold, book, bookmark, bot, box, check-circle, check, chevron-down, chevron-right, chevron-up, code, copy, corner-down-left, download, ellipsis, external-link, eye, file-text, history, image, info, italic, layers, link, loader-spokes, loader, mail, menu, minus, monitor, moon, palette, plus, refresh-cw, search, send, server, sparkles, star, sun, terminal, trash, underline, upload, x-circle, x, zap"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Macro / function Description icon(name, class=\"size-4\") Inline icons/<name>.svg with a class. aria-hidden by default. icon(name, label=\"…\") Meaningful icon: role=\"img\" and aria-label instead of aria-hidden. icon(name, mode=\"img\") <img> of a hashed copy of the file instead of inlining. svg(path, attrs) Inline any SVG under a template root, setting attributes on the root <svg>."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/input",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/input",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/input.md",
      "title": "Input",
      "description": "Text inputs, textareas, selects and file pickers, with focus, disabled, error, warning and success states.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "usage",
          "text": "Usage"
        },
        {
          "level": 2,
          "id": "types",
          "text": "Types"
        },
        {
          "level": 2,
          "id": "file",
          "text": "File"
        },
        {
          "level": 2,
          "id": "textarea",
          "text": "Textarea"
        },
        {
          "level": 2,
          "id": "select",
          "text": "Select"
        },
        {
          "level": 2,
          "id": "sizes",
          "text": "Sizes"
        },
        {
          "level": 2,
          "id": "states",
          "text": "States"
        },
        {
          "level": 3,
          "id": "disabled",
          "text": "Disabled"
        },
        {
          "level": 3,
          "id": "required",
          "text": "Required"
        },
        {
          "level": 3,
          "id": "invalid",
          "text": "Error, warning, success"
        },
        {
          "level": 3,
          "id": "validate-htmx",
          "text": "Validating with htmx"
        },
        {
          "level": 2,
          "id": "combining",
          "text": "Combining"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <input class=\"input max-w-sm\" type=\"email\" placeholder=\"you@example.com\" aria-label=\"Email\" />"
        },
        {
          "id": "usage",
          "title": "Usage",
          "text": "Add .input to any text-like <input>. It fills its container's width; constrain it with a max-w-* utility or a wrapper. Every input needs a label: wrap it in a field with a .field-label, or give it an aria-label. html <div class=\"field max-w-sm\"> <label class=\"field-label\" for=\"input-name\">Name</label> <input class=\"input\" id=\"input-name\" placeholder=\"Ada Lovelace\" /> </div>"
        },
        {
          "id": "types",
          "title": "Types",
          "text": "html <div class=\"grid w-full max-w-sm gap-3\"> <input class=\"input\" type=\"password\" value=\"hunter22\" aria-label=\"Password\" /> <input class=\"input\" type=\"number\" value=\"42\" aria-label=\"Quantity\" /> <input class=\"input\" type=\"date\" aria-label=\"Date\" /> <input class=\"input\" type=\"search\" placeholder=\"Search…\" aria-label=\"Search\" /> </div>"
        },
        {
          "id": "file",
          "title": "File",
          "text": "File inputs keep the native picker, with its button restyled. multiple and accept work as usual. html <div class=\"field max-w-sm\"> <label class=\"field-label\" for=\"input-avatar\">Picture</label> <input class=\"input\" id=\"input-avatar\" type=\"file\" accept=\"image/*\" /> <p class=\"field-description\">PNG or JPG, up to 2 MB.</p> </div> <div class=\"field max-w-sm\"> <label class=\"field-label\" for=\"input-docs\">Documents</label> <input class=\"input\" id=\"input-docs\" type=\"file\" multiple /> </div>"
        },
        {
          "id": "textarea",
          "title": "Textarea",
          "text": ".textarea grows with its content in browsers that support field-sizing. html <textarea class=\"textarea max-w-sm\" placeholder=\"Type your message here.\" aria-label=\"Message\"></textarea>"
        },
        {
          "id": "select",
          "title": "Select",
          "text": ".select styles a native <select>. html <select class=\"select max-w-48\" aria-label=\"Fruit\"> <option>Apple</option> <option>Banana</option> <option>Blueberry</option> </select>"
        },
        {
          "id": "sizes",
          "title": "Sizes",
          "text": ".input-sm and .input-lg match the heights of .btn-sm and .btn-lg. html <div class=\"flex w-full max-w-sm items-center gap-2\"> <input class=\"input input-sm\" placeholder=\"Small\" aria-label=\"Small\" /> <button class=\"btn btn-outline btn-sm\">Go</button> </div> <div class=\"flex w-full max-w-sm items-center gap-2\"> <input class=\"input\" placeholder=\"Default\" aria-label=\"Default\" /> <button class=\"btn btn-outline\">Go</button> </div> <div class=\"flex w-full max-w-sm items-center gap-2\"> <input class=\"input input-lg\" placeholder=\"Large\" aria-label=\"Large\" /> <button class=\"btn btn-outline btn-lg\">Go</button> </div>"
        },
        {
          "id": "states",
          "title": "States",
          "text": ""
        },
        {
          "id": "disabled",
          "title": "Disabled",
          "text": "The disabled attribute fades the input; inside a field, the label fades too. html <div class=\"field max-w-sm\"> <label class=\"field-label\" for=\"input-disabled\">Email</label> <input class=\"input\" id=\"input-disabled\" type=\"email\" placeholder=\"you@example.com\" disabled /> </div>"
        },
        {
          "id": "required",
          "title": "Required",
          "text": "A field holding a required input marks its label with an asterisk automatically. html <div class=\"field max-w-sm\"> <label class=\"field-label\" for=\"input-required\">Username</label> <input class=\"input\" id=\"input-required\" required /> </div>"
        },
        {
          "id": "invalid",
          "title": "Error, warning, success",
          "text": "Set aria-invalid=\"true\" for an error (the border turns red; so does the field's label). Native validation styles the same way once the user has interacted (:user-invalid). For softer states, add .input-warning or .input-success. Pair each with a message from the field classes and link it with aria-describedby. html <div class=\"field-group max-w-sm\"> <div class=\"field\"> <label class=\"field-label\" for=\"input-error\">Email</label> <input class=\"input\" id=\"input-error\" type=\"email\" value=\"ada@\" aria-invalid=\"true\" aria-describedby=\"input-error-msg\" /> <p class=\"field-error\" id=\"input-error-msg\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m15 9-6 6M9 9l6 6\"/></svg> Enter a complete email address.</p> </div> <div class=\"field\"> <label class=\"field-label\" for=\"input-warning\">Password</label> <input class=\"input input-warning\" id=\"input-warning\" type=\"password\" value=\"password\" aria-describedby=\"input-warning-msg\" /> <p class=\"field-warning\" id=\"input-warning-msg\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4M12 17h.01\"/></svg> This password is common. Consider a longer one.</p> </div> <div class=\"field\"> <label class=\"field-label\" for=\"input-success\">Username</label> <input class=\"input input-success\" id=\"input-success\" value=\"ada\" aria-describedby=\"input-success-msg\" /> <p class=\"field-success\" id=\"input-success-msg\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class"
        },
        {
          "id": "validate-htmx",
          "title": "Validating with htmx",
          "text": "The server can return the whole field with its state, swapped in on change. Here the mock server just echoes the value back: html <div class=\"field max-w-sm\"> <label class=\"field-label\" for=\"input-htmx\">Username</label> <input class=\"input\" id=\"input-htmx\" name=\"username\" placeholder=\"Pick a username\" hx-post=\"/api/echo\" hx-trigger=\"change\" hx-target=\"#input-htmx-msg\" /> <p class=\"field-description\" id=\"input-htmx-msg\" aria-live=\"polite\">Checked when you leave the field.</p> </div>"
        },
        {
          "id": "combining",
          "title": "Combining",
          "text": "Join an input to buttons with a button group, put icons, text, badges or buttons inside its border with an input group, and lay out labels, help text and grids of inputs with field. html <div class=\"btn-group w-full max-w-sm\" role=\"group\" aria-label=\"Subscribe\"> <input class=\"input\" type=\"email\" placeholder=\"you@example.com\" aria-label=\"Email\" /> <button class=\"btn btn-outline\">Subscribe</button> </div> <div class=\"input-group max-w-sm\"> <input class=\"input\" placeholder=\"Add tags…\" aria-label=\"Tags\" /> <span class=\"input-group-addon\"><span class=\"badge badge-secondary\">htmx</span><span class=\"badge badge-secondary\">css</span></span> </div>"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class / attribute Description .input Text-like inputs, including type=\"file\". .textarea Multi-line input; grows with its content where supported. .select Native select with a chevron. .input-sm / .input-lg 32px or 44px tall instead of 36px. Also on .select. .input-warning / .input-success Amber or green border and focus ring. aria-invalid=\"true\" Error state: red border and focus ring. :user-invalid looks the same."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/input-group",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/input-group",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/input-group.md",
      "title": "Input group",
      "description": "Icons, text, buttons, menus and spinners inside an input's border, on either side, above or below.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "usage",
          "text": "Usage"
        },
        {
          "level": 2,
          "id": "align",
          "text": "Align"
        },
        {
          "level": 2,
          "id": "icons-text",
          "text": "Icons and text"
        },
        {
          "level": 2,
          "id": "buttons",
          "text": "Buttons"
        },
        {
          "level": 2,
          "id": "badges",
          "text": "Badges"
        },
        {
          "level": 2,
          "id": "dropdown",
          "text": "Dropdown"
        },
        {
          "level": 2,
          "id": "spinner",
          "text": "Spinner"
        },
        {
          "level": 2,
          "id": "textarea",
          "text": "Textarea"
        },
        {
          "level": 2,
          "id": "states",
          "text": "States"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <div class=\"input-group max-w-sm\"> <input class=\"input\" type=\"search\" placeholder=\"Search…\" aria-label=\"Search\" /> <span class=\"input-group-addon\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/></svg></span> <span class=\"input-group-addon input-group-addon-inline-end\">12 results</span> </div>"
        },
        {
          "id": "usage",
          "title": "Usage",
          "text": "Wrap a .input, .textarea or .select in .input-group. The group draws the border and the focus ring; the control drops its own. Add each addon as a .input-group-addon. Put addons after the control in the source. CSS places them, so Tab reaches the control first. States come from the control: aria-invalid=\"true\" turns the whole group red and disabled fades it."
        },
        {
          "id": "align",
          "title": "Align",
          "text": "An addon goes at the inline start by default. .input-group-addon-inline-end puts it at the end, .input-group-addon-block-start above the control and .input-group-addon-block-end below it. html <div class=\"input-group max-w-sm\"> <input class=\"input\" placeholder=\"Inline start\" aria-label=\"Inline start\" /> <span class=\"input-group-addon\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/></svg></span> </div> <div class=\"input-group max-w-sm\"> <input class=\"input\" type=\"password\" placeholder=\"Inline end\" aria-label=\"Password\" /> <span class=\"input-group-addon input-group-addon-inline-end\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg></span> </div> <div class=\"input-group max-w-sm\"> <input class=\"input\" id=\"ig-block-start\" placeholder=\"Block start\" /> <label class=\"input-group-addon input-group-addon-block-start\" for=\"ig-block-start\">Full name</label> </div> <div class=\"input-group max-w-sm\"> <input class=\"input\" placeholder=\"Block end\" aria-label=\"Amount\" /> <span class=\"input-group-addon input-group-addon-block-end\"><span class=\"input-group-text\">Billed in USD</span></span> </div>"
        },
        {
          "id": "icons-text",
          "title": "Icons and text",
          "text": "Addons hold icons, text or both. .input-group-text styles plain text inside a busier addon. html <div class=\"input-group max-w-sm\"> <input class=\"input\" type=\"email\" placeholder=\"you@example.com\" aria-label=\"Email\" /> <span class=\"input-group-addon\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><rect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\"/><path d=\"m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7\"/></svg></span> </div> <div class=\"input-group max-w-sm\"> <input class=\"input\" placeholder=\"example.com\" aria-label=\"Website\" /> <span class=\"input-group-addon\">https://</span> <span class=\"input-group-addon input-group-addon-inline-end\">.com</span> </div> <div class=\"input-group max-w-sm\"> <input class=\"input\" type=\"number\" placeholder=\"0.00\" aria-label=\"Price\" /> <span class=\"input-group-addon\">$</span> <span class=\"input-group-addon input-group-addon-inline-end\">USD</span> </div>"
        },
        {
          "id": "buttons",
          "title": "Buttons",
          "text": "Buttons inside an addon shrink to fit (24px tall). Use any variant; ghost and secondary sit most naturally. html <div class=\"input-group max-w-sm\"> <input class=\"input\" value=\"https://htmx-ui.dev/r/3f9a\" readonly aria-label=\"Share link\" /> <span class=\"input-group-addon input-group-addon-inline-end\"> <button class=\"btn btn-ghost btn-icon\" aria-label=\"Copy\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg></button> </span> </div> <div class=\"input-group max-w-sm\"> <input class=\"input\" type=\"search\" placeholder=\"Search the docs…\" aria-label=\"Search\" /> <span class=\"input-group-addon\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/></svg></span> <span class=\"input-group-addon input-group-addon-inline-end\"> <button class=\"btn btn-secondary\">Search</button> </span> </div>"
        },
        {
          "id": "badges",
          "title": "Badges",
          "text": "html <div class=\"input-group max-w-sm\"> <input class=\"input\" placeholder=\"Add a label…\" aria-label=\"Labels\" /> <span class=\"input-group-addon\"> <span class=\"badge badge-info\">bug</span> <span class=\"badge badge-secondary\">ui</span> </span> </div>"
        },
        {
          "id": "dropdown",
          "title": "Dropdown",
          "text": "A dropdown in an addon, for picking a unit or scope. html <div class=\"input-group max-w-sm\"> <input class=\"input\" placeholder=\"Search…\" aria-label=\"Search\" /> <span class=\"input-group-addon input-group-addon-inline-end\"> <span class=\"dropdown\" data-dropdown> <button class=\"btn btn-ghost\" data-dropdown-trigger aria-haspopup=\"menu\" aria-expanded=\"false\">All files <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"m6 9 6 6 6-6\"/></svg></button> <span class=\"dropdown-menu dropdown-menu-end\" role=\"menu\" hidden> <button class=\"dropdown-item\" role=\"menuitem\">All files</button> <button class=\"dropdown-item\" role=\"menuitem\">Pages</button> <button class=\"dropdown-item\" role=\"menuitem\">Components</button> </span> </span> </span> </div>"
        },
        {
          "id": "spinner",
          "title": "Spinner",
          "text": "A spinner shows work in progress. With .htmx-indicator it appears only while a request runs: type below to search the mock server. html <div class=\"input-group max-w-sm\"> <input class=\"input\" type=\"search\" placeholder=\"Saving…\" disabled aria-label=\"Saving\" /> <span class=\"input-group-addon input-group-addon-inline-end\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" role=\"status\" aria-label=\"Loading\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg></span> </div> <div class=\"input-group max-w-sm\"> <input class=\"input\" type=\"search\" name=\"q\" placeholder=\"Search…\" aria-label=\"Search\" hx-get=\"/api/echo\" hx-trigger=\"input changed delay:300ms\" hx-target=\"#ig-search-result\" hx-indicator=\"#ig-search-spinner\" /> <span class=\"input-group-addon\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/></svg></span> <span class=\"input-group-addon input-group-addon-inline-end\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" id=\"ig-search-spinner\" class=\"spinner htmx-indicator\" role=\"status\" aria-label=\"Searching\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg></span> </div> <p id=\"ig-search-result\" class=\"muted text-sm\" aria-live=\"polite\"></p>"
        },
        {
          "id": "textarea",
          "title": "Textarea",
          "text": "Block addons above or below a textarea make a compose box. A border-t or border-b utility on the addon draws a divider. html <div class=\"input-group max-w-md\"> <textarea class=\"textarea\" placeholder=\"Ask, search or chat…\" aria-label=\"Prompt\"></textarea> <span class=\"input-group-addon input-group-addon-block-start border-b\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"/><path d=\"M14 2v4a2 2 0 0 0 2 2h4M10 9H8M16 13H8M16 17H8\"/></svg> <span class=\"input-group-text\">button.css</span></span> <span class=\"input-group-addon input-group-addon-block-end\"> <button class=\"btn btn-outline btn-icon btn-rounded\" aria-label=\"Attach\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M5 12h14M12 5v14\"/></svg></button> <span class=\"input-group-text ms-auto\">52% used</span> <button class=\"btn btn-primary btn-icon btn-rounded\" aria-label=\"Send\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z\"/><path d=\"m21.854 2.147-10.94 10.939\"/></svg></button> </span> </div>"
        },
        {
          "id": "states",
          "title": "States",
          "text": "html <div class=\"field max-w-sm\"> <label class=\"field-label\" for=\"ig-invalid\">Username</label> <div class=\"input-group\"> <input class=\"input\" id=\"ig-invalid\" value=\"ada lovelace\" aria-invalid=\"true\" aria-describedby=\"ig-invalid-msg\" /> <span class=\"input-group-addon\">@</span> </div> <p class=\"field-error\" id=\"ig-invalid-msg\">Usernames can't contain spaces.</p> </div> <div class=\"input-group max-w-sm\"> <input class=\"input\" placeholder=\"Disabled\" disabled aria-label=\"Disabled\" /> <span class=\"input-group-addon\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><rect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\"/><path d=\"m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7\"/></svg></span> </div>"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .input-group Border and focus ring around a control and its addons. .input-group-addon Addon at the inline start. Holds icons, text, badges, buttons, menus or spinners. .input-group-addon-inline-end Addon at the end of the row. .input-group-addon-block-start Full-width addon above the control. .input-group-addon-block-end Full-width addon below the control. .input-group-text Plain muted text inside an addon."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/popover",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/popover",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/popover.md",
      "title": "Popover",
      "description": "A floating panel of rich content, such as text or a small form, that opens from a button.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "markup",
          "text": "Markup"
        },
        {
          "level": 2,
          "id": "keyboard",
          "text": "Closing"
        },
        {
          "level": 2,
          "id": "alignment",
          "text": "Alignment"
        },
        {
          "level": 2,
          "id": "htmx",
          "text": "Content from the server"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <div class=\"popover\" data-popover> <button class=\"btn btn-outline\" data-popover-trigger aria-expanded=\"false\">Dimensions</button> <div class=\"popover-content\" data-popover-content hidden> <p class=\"popover-title\">Dimensions</p> <p class=\"popover-description\">Set the dimensions for the layer.</p> <div class=\"field-group mt-4 gap-3\"> <div class=\"field field-horizontal\"> <label class=\"field-label w-16\" for=\"pop-width\">Width</label> <input class=\"input input-sm\" id=\"pop-width\" value=\"100%\" autofocus /> </div> <div class=\"field field-horizontal\"> <label class=\"field-label w-16\" for=\"pop-height\">Height</label> <input class=\"input input-sm\" id=\"pop-height\" value=\"25px\" /> </div> </div> </div> </div>"
        },
        {
          "id": "markup",
          "title": "Markup",
          "text": "Wrap the trigger and panel in .popover with data-popover. The trigger gets data-popover-trigger and aria-expanded=\"false\". The behaviour links it to the panel with aria-controls. The panel gets .popover-content, data-popover-content and starts hidden. Focus stays on the trigger when it opens, unless an element in the panel has autofocus. For a list of actions, use a dropdown instead: it adds menu semantics and arrow-key navigation."
        },
        {
          "id": "keyboard",
          "title": "Closing",
          "text": "Esc closes the popover and returns focus to the trigger. Clicking outside or tabbing out of it also closes it."
        },
        {
          "id": "alignment",
          "title": "Alignment",
          "text": "The panel opens below the trigger, aligned to its left edge. .popover-content-center and .popover-content-end align it to the centre or right edge; .popover-content-up opens it above. html <div class=\"popover\" data-popover> <button class=\"btn btn-ghost btn-icon\" data-popover-trigger aria-expanded=\"false\" aria-label=\"About this metric\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4M12 8h.01\"/></svg></button> <div class=\"popover-content popover-content-center popover-content-up w-60\" data-popover-content hidden> <p class=\"popover-description mt-0\">Active users counts everyone who signed in during the last 28 days.</p> </div> </div>"
        },
        {
          "id": "htmx",
          "title": "Content from the server",
          "text": "The panel is an ordinary element, so htmx can fill it. This one loads its content the first time it's opened: html <div class=\"popover\" data-popover> <button class=\"btn btn-outline\" data-popover-trigger aria-expanded=\"false\" hx-get=\"/api/hello\" hx-trigger=\"click once\" hx-target=\"next [data-popover-content]\">Server message</button> <div class=\"popover-content w-80 p-2\" data-popover-content hidden><p class=\"p-2 muted\">Loading…</p></div> </div>"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .popover Positioning wrapper around the trigger and panel. Add data-popover. .popover-content The floating panel. Add data-popover-content and hidden. .popover-content-center / -end Centre the panel on the trigger, or align it to the right edge. .popover-content-up Open above the trigger. .popover-title Heading text in the panel. .popover-description Muted supporting text."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/sidebar",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/sidebar",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/sidebar.md",
      "title": "Sidebar",
      "description": "A navigation column that sticks beside the content on large screens and slides in over it on small ones. The navigation on this page is one.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "preview",
          "text": "Preview"
        },
        {
          "level": 2,
          "id": "responsive",
          "text": "Responsive layout"
        },
        {
          "level": 2,
          "id": "behaviour",
          "text": "Behaviour"
        },
        {
          "level": 2,
          "id": "active",
          "text": "Marking the current page"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "preview",
          "title": "Preview",
          "text": "The links and labels, shown in a fixed-size frame: html <div class=\"flex h-72 text-left\"> <nav class=\"w-56 shrink-0 overflow-y-auto border-r border-border p-4\"> <div class=\"sidebar-group\"> <p class=\"sidebar-label\">Workspace</p> <a href=\"#preview\" class=\"sidebar-link\" aria-current=\"page\">Dashboard</a> <a href=\"#preview\" class=\"sidebar-link\">Projects <span class=\"badge badge-secondary\">4</span></a> <a href=\"#preview\" class=\"sidebar-link\">Deployments</a> </div> <div class=\"sidebar-group\"> <p class=\"sidebar-label\">Account</p> <a href=\"#preview\" class=\"sidebar-link\">Settings</a> <a href=\"#preview\" class=\"sidebar-link\">Billing</a> </div> </nav> <div class=\"flex-1 p-6\"><p class=\"muted\">Page content</p></div> </div>"
        },
        {
          "id": "responsive",
          "title": "Responsive layout",
          "text": "Put .sidebar on an <aside> next to your content. From the lg breakpoint up it's a sticky column under a 56px header. Below that it hides off-screen until a toggle opens it. Resize this window to try it with the docs navigation. layout.html <header class=\"sticky top-0 h-14 border-b\"> <button class=\"btn btn-ghost btn-icon lg:hidden\" data-sidebar-toggle aria-controls=\"nav\" aria-expanded=\"false\" aria-label=\"Open navigation\"> <!-- menu icon <!-- menu icon --> </button> </header> <div class=\"mx-auto flex max-w-7xl gap-12 px-4\"> <aside id=\"nav\" class=\"sidebar\" data-sidebar> <nav class=\"sidebar-group\"> <p class=\"sidebar-label\">Getting started</p> <a href=\"/docs\" class=\"sidebar-link\" aria-current=\"page\">Introduction</a> <a href=\"/docs/installation\" class=\"sidebar-link\">Installation</a> </nav> </aside> <div class=\"sidebar-backdrop\" data-sidebar-close></div> <main class=\"min-w-0 flex-1\">…</main> </div>"
        },
        {
          "id": "behaviour",
          "title": "Behaviour",
          "text": "A [data-sidebar-toggle] opens or closes the sidebar named by its aria-controls, and keeps aria-expanded in sync. The open sidebar has a data-open attribute, which slides it in and shows the backdrop. It closes on Esc, a click on [data-sidebar-close], or a click on any link inside it."
        },
        {
          "id": "active",
          "title": "Marking the current page",
          "text": "Set aria-current=\"page\" on the active link. It's both the style hook and what screen readers announce. Nunjucks <a href=\"{{ item.href }}\" class=\"sidebar-link\" {% if item.href == url %}aria-current=\"page\"{% endif %}>{{ item.title }}</a>"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .sidebar Off-canvas panel below lg; sticky 240px column at lg and up. .sidebar-backdrop Dimmed overlay shown while the sidebar is open on small screens. .sidebar-group A block of links; adds space between groups. .sidebar-label Small uppercase group heading. .sidebar-link Navigation link. Styled as active with aria-current=\"page\". [data-sidebar-toggle] Behaviour: opens/closes the sidebar named in aria-controls. [data-sidebar-close] Behaviour: closes the sidebar (use on the backdrop)."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/spinner",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/spinner",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/spinner.md",
      "title": "Spinner",
      "description": "An indicator that shows a loading state. Any icon can spin, or use a pure-CSS ring when there is no icon to hand.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "usage",
          "text": "Usage"
        },
        {
          "level": 2,
          "id": "icon",
          "text": "Custom icon"
        },
        {
          "level": 2,
          "id": "css-ring",
          "text": "Without an icon"
        },
        {
          "level": 2,
          "id": "sizes",
          "text": "Sizes"
        },
        {
          "level": 2,
          "id": "colour",
          "text": "Colour"
        },
        {
          "level": 2,
          "id": "button",
          "text": "In a button"
        },
        {
          "level": 2,
          "id": "badge",
          "text": "In a badge"
        },
        {
          "level": 2,
          "id": "button-group",
          "text": "In a button group"
        },
        {
          "level": 2,
          "id": "input-group",
          "text": "In an input group"
        },
        {
          "level": 2,
          "id": "htmx",
          "text": "With htmx requests"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" role=\"status\" aria-label=\"Loading\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg>"
        },
        {
          "id": "usage",
          "title": "Usage",
          "text": "Add .spinner to an SVG icon and it spins. The spinner() macro inlines an icon from icons/ with the class and accessibility attributes set: role=\"status\" and an aria-label of \"Loading\". html <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" role=\"status\" aria-label=\"Saving\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg>"
        },
        {
          "id": "icon",
          "title": "Custom icon",
          "text": "Pass any icon name to the macro, or put .spinner on your own SVG. html <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" role=\"status\" aria-label=\"Loading\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" role=\"status\" aria-label=\"Loading\"><path d=\"M12 2v4M16.2 7.8l2.9-2.9M18 12h4M16.2 16.2l2.9 2.9M12 18v4M4.9 19.1l2.9-2.9M2 12h4M4.9 4.9l2.9 2.9\"/></svg> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" role=\"status\" aria-label=\"Loading\"><path d=\"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8\"/><path d=\"M21 3v5h-5\"/><path d=\"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16\"/><path d=\"M8 16H3v5\"/></svg>"
        },
        {
          "id": "css-ring",
          "title": "Without an icon",
          "text": "On any element other than an SVG, .spinner draws a ring with CSS. Use it in markup that can't inline icons, such as HTML fragments built by your server. html <span class=\"spinner\" role=\"status\" aria-label=\"Loading\"></span> <span class=\"spinner spinner-lg text-primary\" role=\"status\" aria-label=\"Loading\"></span>"
        },
        {
          "id": "sizes",
          "title": "Sizes",
          "text": "Use .spinner-sm (12px), the default (16px), .spinner-lg (24px) and .spinner-xl (32px), or any size-* utility. html <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner spinner-sm\" role=\"status\" aria-label=\"Loading\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" role=\"status\" aria-label=\"Loading\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner spinner-lg\" role=\"status\" aria-label=\"Loading\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner spinner-xl\" role=\"status\" aria-label=\"Loading\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner size-12\" role=\"status\" aria-label=\"Loading\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg>"
        },
        {
          "id": "colour",
          "title": "Colour",
          "text": "Spinners use the current text colour, so text-* utilities recolour them. html <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner spinner-lg text-primary\" role=\"status\" aria-label=\"Loading\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner spinner-lg text-success\" role=\"status\" aria-label=\"Loading\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner spinner-lg text-warning\" role=\"status\" aria-label=\"Loading\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner spinner-lg text-muted-foreground\" role=\"status\" aria-label=\"Loading\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg>"
        },
        {
          "id": "button",
          "title": "In a button",
          "text": "Inside a .btn, the spinner takes the button's icon size. When the button text already says what's happening, pass label=none so the spinner is hidden from screen readers. aria-busy=\"true\" makes the button ignore clicks without fading it like disabled does. html <button class=\"btn btn-primary\" aria-busy=\"true\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> Saving</button> <button class=\"btn btn-outline\" disabled><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> Please wait</button> <button class=\"btn btn-secondary btn-sm\" disabled><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> Loading</button> <button class=\"btn btn-outline btn-icon\" aria-busy=\"true\" aria-label=\"Refresh\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg></button>"
        },
        {
          "id": "badge",
          "title": "In a badge",
          "text": "html <span class=\"badge badge-secondary\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> Syncing</span> <span class=\"badge badge-outline\"><span class=\"spinner\" aria-hidden=\"true\"></span> Deploying</span> <span class=\"badge badge-info\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" aria-hidden=\"true\"><path d=\"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8\"/><path d=\"M21 3v5h-5\"/><path d=\"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16\"/><path d=\"M8 16H3v5\"/></svg> Updating</span>"
        },
        {
          "id": "button-group",
          "title": "In a button group",
          "text": "html <div class=\"btn-group\" role=\"group\" aria-label=\"Sync\"> <button class=\"btn btn-outline\" aria-busy=\"true\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> Syncing</button> <button class=\"btn btn-outline btn-icon\" aria-label=\"Cancel\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg></button> </div>"
        },
        {
          "id": "input-group",
          "title": "In an input group",
          "text": "html <div class=\"input-group max-w-sm\"> <input class=\"input\" type=\"search\" value=\"htmx\" aria-label=\"Search\" /> <span class=\"input-group-addon\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/></svg></span> <span class=\"input-group-addon input-group-addon-inline-end\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner\" role=\"status\" aria-label=\"Loading\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg></span> </div>"
        },
        {
          "id": "htmx",
          "title": "With htmx requests",
          "text": "Add .htmx-indicator to show a spinner only while a request runs. Inside a button, badge or input-group addon it takes no space until then. Add .htmx-indicator-hide to an icon to swap it for the spinner. html <div class=\"flex flex-wrap items-center gap-3\"> <button class=\"btn btn-primary\" hx-post=\"/api/slow\" hx-target=\"#spinner-result\" hx-disable=\"this\"> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner htmx-indicator\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> Save </button> <button class=\"btn btn-outline\" hx-post=\"/api/slow\" hx-target=\"#spinner-result\" hx-disable=\"this\"> <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"htmx-indicator-hide\" aria-hidden=\"true\"><path d=\"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8\"/><path d=\"M21 3v5h-5\"/><path d=\"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16\"/><path d=\"M8 16H3v5\"/></svg><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"spinner htmx-indicator\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-6.22-8.56\"/></svg> Refresh </button> </div> <p id=\"spinner-result\" class=\"muted\" aria-live=\"polite\"></p> The /api/slow endpoint is a mock that only exists under bun run dev."
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .spinner Spins an SVG icon. On any other element, draws a CSS ring. .spinner-sm / -lg / -xl 12px, 24px or 32px instead of 16px. .htmx-indicator-hide Hidden while the htmx request it's inside runs. Macro Description spinner(icon=\"loader\", class=\"\", label=\"Loading\", id=none) Inlines icons/<icon>.svg as a spinner. label=none hides it from assistive technology."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/table",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/table",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/table.md",
      "title": "Table",
      "description": "Readable data tables with a bordered, horizontally scrollable wrapper.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "usage",
          "text": "Usage"
        },
        {
          "level": 2,
          "id": "htmx",
          "text": "Rows from the server"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <div class=\"table-wrap\"> <table class=\"table table-hover\"> <caption>Recent invoices</caption> <thead> <tr><th>Invoice</th><th>Status</th><th>Method</th><th class=\"text-right\">Amount</th></tr> </thead> <tbody> <tr> <td class=\"font-medium\">INV-001</td> <td><span class=\"badge badge-success\">Paid</span></td> <td>Credit card</td> <td class=\"text-right tabular-nums\">$250.00</td> </tr> <tr> <td class=\"font-medium\">INV-002</td> <td><span class=\"badge badge-warning\">Pending</span></td> <td>Bank transfer</td> <td class=\"text-right tabular-nums\">$150.00</td> </tr> <tr> <td class=\"font-medium\">INV-003</td> <td><span class=\"badge badge-danger\">Overdue</span></td> <td>Credit card</td> <td class=\"text-right tabular-nums\">$350.00</td> </tr> <tr> <td class=\"font-medium\">INV-004</td> <td><span class=\"badge badge-success\">Paid</span></td> <td>PayPal</td> <td class=\"text-right tabular-nums\">$450.00</td> </tr> </tbody> </table> </div>"
        },
        {
          "id": "usage",
          "title": "Usage",
          "text": "Wrap the table in .table-wrap for the border, rounded corners and horizontal scrolling on narrow screens. Add .table-hover to highlight rows under the pointer. Align numbers with text-right tabular-nums. A <caption> renders below the table as muted text."
        },
        {
          "id": "htmx",
          "title": "Rows from the server",
          "text": "Have the server return <tr> fragments and append them to the <tbody> with hx-swap=\"beforeend\". Badges in the new rows are plain classes, so they need no initialisation. html <div class=\"table-wrap\"> <table class=\"table\"> <thead><tr><th>Invoice</th><th>Status</th><th class=\"text-right\">Amount</th></tr></thead> <tbody id=\"invoice-rows\"> <tr><td class=\"font-medium\">INV-001</td><td><span class=\"badge badge-success\">Paid</span></td><td class=\"text-right tabular-nums\">$250.00</td></tr> </tbody> </table> </div> <button class=\"btn btn-outline btn-sm mt-4\" hx-get=\"/api/invoices\" hx-target=\"#invoice-rows\" hx-swap=\"beforeend\">Load more</button>"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .table-wrap Bordered, rounded, horizontally scrollable wrapper. .table Base table styles: header row, cell padding, row dividers. .table-hover Highlights the row under the pointer."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/tabs",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/tabs",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/tabs.md",
      "title": "Tabs",
      "description": "Switch between panels of related content. Groups can sync with each other and remember the reader's choice.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "markup",
          "text": "Markup"
        },
        {
          "level": 2,
          "id": "line",
          "text": "Underlined"
        },
        {
          "level": 2,
          "id": "sync",
          "text": "Synced and remembered"
        },
        {
          "level": 2,
          "id": "htmx",
          "text": "Lazy panels with htmx"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <div class=\"tabs mx-auto max-w-md\" data-tabs> <div class=\"tabs-list\" role=\"tablist\" aria-label=\"Account\"> <button type=\"button\" class=\"tabs-trigger\" role=\"tab\" data-tab=\"account\" aria-selected=\"true\">Account</button> <button type=\"button\" class=\"tabs-trigger\" role=\"tab\" data-tab=\"password\" aria-selected=\"false\">Password</button> </div> <div class=\"tabs-panel\" role=\"tabpanel\" data-tab-panel=\"account\"> <p class=\"muted\">Change your name and email address.</p> </div> <div class=\"tabs-panel\" role=\"tabpanel\" data-tab-panel=\"password\" hidden> <p class=\"muted\">Change your password. You'll be signed out on other devices.</p> </div> </div>"
        },
        {
          "id": "markup",
          "title": "Markup",
          "text": "Each [data-tab] button shows the [data-tab-panel] with the same value and hides the others. Mark the initial tab with aria-selected=\"true\" and add hidden to the other panels, so the page is right before JavaScript runs. The behaviour fills in id, aria-controls and aria-labelledby, and moves focus with ← → Home End."
        },
        {
          "id": "line",
          "title": "Underlined",
          "text": "html <div class=\"tabs mx-auto max-w-md\" data-tabs> <div class=\"tabs-list tabs-line\" role=\"tablist\"> <button type=\"button\" class=\"tabs-trigger\" role=\"tab\" data-tab=\"overview\" aria-selected=\"true\">Overview</button> <button type=\"button\" class=\"tabs-trigger\" role=\"tab\" data-tab=\"activity\" aria-selected=\"false\">Activity</button> <button type=\"button\" class=\"tabs-trigger\" role=\"tab\" data-tab=\"settings\" aria-selected=\"false\">Settings</button> </div> <div class=\"tabs-panel\" role=\"tabpanel\" data-tab-panel=\"overview\"><p class=\"muted\">Overview panel</p></div> <div class=\"tabs-panel\" role=\"tabpanel\" data-tab-panel=\"activity\" hidden><p class=\"muted\">Activity panel</p></div> <div class=\"tabs-panel\" role=\"tabpanel\" data-tab-panel=\"settings\" hidden><p class=\"muted\">Settings panel</p></div> </div>"
        },
        {
          "id": "sync",
          "title": "Synced and remembered",
          "text": "Give groups the same data-tabs-sync key and they switch together. The choice is saved to localStorage (key tabs:<key>), restored on the next visit, and followed in other open browser tabs. The package-manager tabs on code blocks use data-tabs-sync=\"pm\". Switch one of these two: html <div class=\"tabs\" data-tabs data-tabs-sync=\"demo-lang\"> <div class=\"tabs-list\" role=\"tablist\" aria-label=\"Language 1\"> <button type=\"button\" class=\"tabs-trigger\" role=\"tab\" data-tab=\"html\" aria-selected=\"true\">HTML</button> <button type=\"button\" class=\"tabs-trigger\" role=\"tab\" data-tab=\"css\" aria-selected=\"false\">CSS</button> </div> <div class=\"tabs-panel\" role=\"tabpanel\" data-tab-panel=\"html\"><p class=\"muted\">Group 1: HTML panel</p></div> <div class=\"tabs-panel\" role=\"tabpanel\" data-tab-panel=\"css\" hidden><p class=\"muted\">Group 1: CSS panel</p></div> </div> <div class=\"tabs\" data-tabs data-tabs-sync=\"demo-lang\"> <div class=\"tabs-list\" role=\"tablist\" aria-label=\"Language 2\"> <button type=\"button\" class=\"tabs-trigger\" role=\"tab\" data-tab=\"html\" aria-selected=\"true\">HTML</button> <button type=\"button\" class=\"tabs-trigger\" role=\"tab\" data-tab=\"css\" aria-selected=\"false\">CSS</button> </div> <div class=\"tabs-panel\" role=\"tabpanel\" data-tab-panel=\"html\"><p class=\"muted\">Group 2: HTML panel</p></div> <div class=\"tabs-panel\" role=\"tabpanel\" data-tab-panel=\"css\" hidden><p class=\"muted\">Group 2: CSS panel</p></div> </div>"
        },
        {
          "id": "htmx",
          "title": "Lazy panels with htmx",
          "text": "Load a panel's content the first time it's shown with hx-trigger=\"intersect once\": html <div class=\"tabs mx-auto max-w-md\" data-tabs> <div class=\"tabs-list\" role=\"tablist\"> <button type=\"button\" class=\"tabs-trigger\" role=\"tab\" data-tab=\"local\" aria-selected=\"true\">Local</button> <button type=\"button\" class=\"tabs-trigger\" role=\"tab\" data-tab=\"server\" aria-selected=\"false\">From server</button> </div> <div class=\"tabs-panel\" role=\"tabpanel\" data-tab-panel=\"local\"><p class=\"muted\">Rendered with the page.</p></div> <div class=\"tabs-panel\" role=\"tabpanel\" data-tab-panel=\"server\" hidden> <div hx-get=\"/api/hello\" hx-trigger=\"intersect once\"><p class=\"muted\">Loading…</p></div> </div> </div>"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .tabs-list Segmented control holding the tab buttons. .tabs-trigger Tab button; selected style comes from aria-selected=\"true\". .tabs-panel Panel spacing and focus ring. .tabs-line Underlined variant of .tabs-list. [data-tabs] Behaviour: wires up a tab group. [data-tab] / [data-tab-panel] Matching values pair a tab with its panel. [data-tabs-sync=\"key\"] Sync groups with the same key and remember the choice."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/text",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/text",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/text.md",
      "title": "Text",
      "description": "Style long-form content without a class on every element: wrap it in .prose from @tailwindcss/typography, tuned to htmx-ui's tokens. Small helper classes cover single elements in UI chrome.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "prose",
          "text": "Prose"
        },
        {
          "level": 2,
          "id": "customisation",
          "text": "What htmx-ui changes"
        },
        {
          "level": 2,
          "id": "components",
          "text": "Components inside prose"
        },
        {
          "level": 2,
          "id": "sizes",
          "text": "Sizes"
        },
        {
          "level": 2,
          "id": "custom",
          "text": "Customising further"
        },
        {
          "level": 2,
          "id": "helpers",
          "text": "Single elements"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "prose",
          "title": "Prose",
          "text": "Wrap plain HTML (an article, Markdown output, CMS content, a server-rendered fragment) in .prose. Every heading, paragraph, list, link, quote, table and code element inside is styled. No per-element classes. html <article class=\"prose\"> <h2>Shipping hypermedia</h2> <p class=\"lead\">Your server already renders HTML. htmx swaps it in; htmx-ui makes it look good.</p> <p>Return a fragment from any endpoint, point <code>hx-target</code> at it, and you're done. See <a href=\"/docs/installation\">installation</a>.</p> <ul> <li>Plain <strong>HTML</strong> in, styled content out</li> <li>Works with Markdown renderers and CMS output</li> </ul> <blockquote><p>Hypermedia is the engine of application state.</p></blockquote> <p>Press <kbd>Ctrl</kbd> + <kbd>K</kbd> to search.</p> </article> .prose comes from @tailwindcss/typography, which htmx-ui/styles.css loads for you. Everything in its docs works: sizes (prose-sm, prose-lg), element modifiers (prose-a:text-primary, prose-headings:tracking-tight), and max-w-none to drop the default line-length limit."
        },
        {
          "id": "customisation",
          "title": "What htmx-ui changes",
          "text": "Colours come from the tokens: body and headings use --foreground, links --primary, rules and table borders --border, code blocks --code-background. Prose therefore follows custom themes and dark mode on its own. Don't add dark:prose-invert. Inline code is a pill on a muted background instead of being wrapped in backticks. Links get a soft underline that firms up on hover. Headings get scroll-margin-top so #anchor links clear a sticky header. Components placed inside prose with not-prose (alerts, code blocks, tables) get the same vertical spacing as paragraphs."
        },
        {
          "id": "components",
          "title": "Components inside prose",
          "text": "Prose styles every element inside it, which would also restyle a component's markup. Add not-prose to a component to keep its own styles. htmx-ui's Nunjucks macros (code, alert) already do. The flip side: .prose placed inside a not-prose element gets no styles at all, so don't nest them that way. html <article class=\"prose\"> <p>Before you deploy:</p> <div class=\"alert alert-warning not-prose\" role=\"status\"> <div class=\"alert-title\">Back up your database</div> </div> <p>Then run the migration.</p> </article>"
        },
        {
          "id": "sizes",
          "title": "Sizes",
          "text": "html <div class=\"prose prose-sm\"><p><strong>prose-sm</strong>: for sidebars, cards and dense UIs.</p></div> <div class=\"prose\"><p><strong>prose</strong>: the default, for articles and docs.</p></div> <div class=\"prose prose-lg\"><p><strong>prose-lg</strong>: for landing-page copy.</p></div>"
        },
        {
          "id": "custom",
          "title": "Customising further",
          "text": "Tweak one block with element modifiers, or change prose everywhere by adding to the prose utility: html <article class=\"prose prose-headings:font-bold prose-a:no-underline\">…</article> src/styles.css @utility prose { --tw-prose-bullets: var(--primary); & :where(h2):not(:where([class~=\"not-prose\"] *)) { border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; } }"
        },
        {
          "id": "helpers",
          "title": "Single elements",
          "text": "UI chrome (a page title, a hint under a form field) isn't long-form content. For one-off elements outside prose, use these helpers: html <p class=\"eyebrow\">Settings</p> <h1 class=\"h1\">Account</h1> <p class=\"lead\">Manage your profile and preferences.</p> <h3 class=\"h3\">Danger zone</h3> <p class=\"muted\">Deleting your account can't be undone.</p> <p>Read the <a class=\"link\" href=\"/docs/theming\">theming guide</a>, run <code class=\"code\">bun run dev</code>, press <kbd class=\"kbd\">Esc</kbd>.</p> Which one? More than a couple of text elements in a row is content: use .prose. A single label or title in a layout: use a helper."
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class Description .prose Long-form content (@tailwindcss/typography), mapped to htmx-ui tokens. .prose-sm / .prose-lg Smaller / larger prose. .not-prose Opt a subtree (usually a component) out of prose styles. .h1 / .h2 / .h3 / .h4 Heading scale for single titles outside prose. .lead Intro paragraph: larger, muted. .muted Small secondary text. .eyebrow Small primary-coloured label above a heading. .link Underlined link in the primary colour. .code Inline code outside prose. .kbd Keyboard key outside prose."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/toggle",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/toggle",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/toggle.md",
      "title": "Toggle",
      "description": "A two-state button that is either on or off. Any button variant can be a toggle.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "usage",
          "text": "Usage"
        },
        {
          "level": 2,
          "id": "variants",
          "text": "Any button"
        },
        {
          "level": 2,
          "id": "sizes",
          "text": "Sizes and shapes"
        },
        {
          "level": 2,
          "id": "disabled",
          "text": "Disabled"
        },
        {
          "level": 2,
          "id": "htmx",
          "text": "Saving with htmx"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <button class=\"btn btn-toggle btn-icon\" data-toggle aria-pressed=\"false\" aria-label=\"Bold\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8\"/></svg></button>"
        },
        {
          "id": "usage",
          "title": "Usage",
          "text": "Add data-toggle and aria-pressed=\"false\" (or \"true\" to start on) to any .btn. Each click flips aria-pressed, which screen readers announce as \"pressed\" or \"not pressed\". Styles key off the same attribute. The button fires a bubbling toggle:change event with detail.pressed. Keep the label the same in both states. \"Bold\" pressed reads correctly; switching the text to \"Not bold\" doesn't."
        },
        {
          "id": "variants",
          "title": "Any button",
          "text": ".btn-toggle is the dedicated look: muted when off, filled when on. Every other variant works too: ghost and outline fill when on, secondary turns the brand colour, and primary and danger are outlined when off and solid when on. Click them: html <button class=\"btn btn-toggle\" data-toggle aria-pressed=\"true\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M19 4h-9M14 20H5M15 4 9 20\"/></svg> Toggle</button> <button class=\"btn btn-ghost\" data-toggle aria-pressed=\"false\">Ghost</button> <button class=\"btn btn-outline\" data-toggle aria-pressed=\"false\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z\"/></svg> Outline</button> <button class=\"btn btn-secondary\" data-toggle aria-pressed=\"false\">Secondary</button> <button class=\"btn btn-primary\" data-toggle aria-pressed=\"false\">Primary</button> <button class=\"btn btn-danger\" data-toggle aria-pressed=\"false\">Danger</button> <button class=\"btn btn-link\" data-toggle aria-pressed=\"false\">Link</button>"
        },
        {
          "id": "sizes",
          "title": "Sizes and shapes",
          "text": "html <button class=\"btn btn-toggle btn-outline btn-icon btn-sm\" data-toggle aria-pressed=\"false\" aria-label=\"Underline\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M6 4v6a6 6 0 0 0 12 0V4M4 20h16\"/></svg></button> <button class=\"btn btn-toggle btn-outline btn-icon\" data-toggle aria-pressed=\"true\" aria-label=\"Underline\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M6 4v6a6 6 0 0 0 12 0V4M4 20h16\"/></svg></button> <button class=\"btn btn-toggle btn-outline btn-icon btn-lg\" data-toggle aria-pressed=\"false\" aria-label=\"Underline\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M6 4v6a6 6 0 0 0 12 0V4M4 20h16\"/></svg></button> <button class=\"btn btn-outline btn-rounded btn-sm\" data-toggle aria-pressed=\"false\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z\"/></svg> Save</button>"
        },
        {
          "id": "disabled",
          "title": "Disabled",
          "text": "html <button class=\"btn btn-toggle btn-icon\" data-toggle aria-pressed=\"false\" aria-label=\"Bold\" disabled><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8\"/></svg></button> <button class=\"btn btn-toggle btn-icon\" data-toggle aria-pressed=\"true\" aria-label=\"Italic\" disabled><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M19 4h-9M14 20H5M15 4 9 20\"/></svg></button>"
        },
        {
          "id": "htmx",
          "title": "Saving with htmx",
          "text": "Trigger the request on toggle:change rather than click, so it runs after aria-pressed has flipped, and send the new state with hx-vals. html <button class=\"btn btn-outline\" data-toggle aria-pressed=\"false\" hx-post=\"/api/echo\" hx-trigger=\"toggle:change\" hx-vals='js:{starred: this.getAttribute(\"aria-pressed\")}' hx-target=\"#toggle-result\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z\"/></svg> Star</button> <p id=\"toggle-result\" class=\"muted\" aria-live=\"polite\"></p>"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class / attribute Description .btn-toggle Toggle look: muted when off, filled when on. Combine with .btn-outline for a bordered one. data-toggle Flips aria-pressed on click and fires toggle:change { pressed }. aria-pressed \"true\" or \"false\". Drives the pressed style on every button variant."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/toggle-group",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/toggle-group",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/components/toggle-group.md",
      "title": "Toggle group",
      "description": "A set of two-state buttons. Pick exactly one, at most one, or any number.",
      "section": "Components",
      "headings": [
        {
          "level": 2,
          "id": "usage",
          "text": "Usage"
        },
        {
          "level": 2,
          "id": "single",
          "text": "Single"
        },
        {
          "level": 2,
          "id": "multiple",
          "text": "Multiple"
        },
        {
          "level": 2,
          "id": "joined",
          "text": "Joined or spaced"
        },
        {
          "level": 2,
          "id": "vertical",
          "text": "Vertical"
        },
        {
          "level": 2,
          "id": "disabled",
          "text": "Disabled"
        },
        {
          "level": 2,
          "id": "htmx",
          "text": "With htmx"
        },
        {
          "level": 2,
          "id": "reference",
          "text": "Reference"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "html <div class=\"toggle-group btn-group\" role=\"group\" aria-label=\"Text alignment\" data-toggle-group=\"single\" data-toggle-group-required> <button class=\"btn btn-outline btn-icon\" aria-pressed=\"true\" value=\"left\" aria-label=\"Align left\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M15 12H3M17 18H3M21 6H3\"/></svg></button> <button class=\"btn btn-outline btn-icon\" aria-pressed=\"false\" value=\"center\" aria-label=\"Align center\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M17 12H7M19 18H5M21 6H3\"/></svg></button> <button class=\"btn btn-outline btn-icon\" aria-pressed=\"false\" value=\"right\" aria-label=\"Align right\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M21 12H9M21 18H7M21 6H3\"/></svg></button> </div> Use a toggle group for buttons that switch a state. For buttons that perform actions, use a button group."
        },
        {
          "id": "usage",
          "title": "Usage",
          "text": "Wrap the buttons in .toggle-group with data-toggle-group, role=\"group\" and an aria-label. Each button gets aria-pressed, and a value to identify it. They don't need data-toggle; the group handles them. The group is one tab stop. ← → ↑ ↓ Home End move between buttons; Space or Enter presses. Every change fires a bubbling toggle-group:change event with detail.value: the pressed buttons' values."
        },
        {
          "id": "single",
          "title": "Single",
          "text": "data-toggle-group=\"single\": pressing a button releases the others. Pressing the pressed one releases it, leaving none. Add data-toggle-group-required to keep exactly one pressed, as in the alignment example at the top. html <div class=\"toggle-group\" role=\"group\" aria-label=\"Plan\" data-toggle-group=\"single\"> <button class=\"btn btn-toggle btn-sm\" aria-pressed=\"false\" value=\"monthly\">Monthly</button> <button class=\"btn btn-toggle btn-sm\" aria-pressed=\"true\" value=\"yearly\">Yearly</button> <button class=\"btn btn-toggle btn-sm\" aria-pressed=\"false\" value=\"lifetime\">Lifetime</button> </div>"
        },
        {
          "id": "multiple",
          "title": "Multiple",
          "text": "data-toggle-group=\"multiple\": each button toggles on its own. html <div class=\"toggle-group\" role=\"group\" aria-label=\"Text formatting\" data-toggle-group=\"multiple\"> <button class=\"btn btn-toggle btn-icon\" aria-pressed=\"true\" value=\"bold\" aria-label=\"Bold\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8\"/></svg></button> <button class=\"btn btn-toggle btn-icon\" aria-pressed=\"false\" value=\"italic\" aria-label=\"Italic\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M19 4h-9M14 20H5M15 4 9 20\"/></svg></button> <button class=\"btn btn-toggle btn-icon\" aria-pressed=\"false\" value=\"underline\" aria-label=\"Underline\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M6 4v6a6 6 0 0 0 12 0V4M4 20h16\"/></svg></button> </div>"
        },
        {
          "id": "joined",
          "title": "Joined or spaced",
          "text": "A .toggle-group spaces its buttons. Add .btn-group to join them like a button group, with all of its options: sizes, vertical, separators. html <div class=\"toggle-group btn-group btn-group-sm\" role=\"group\" aria-label=\"Size\" data-toggle-group=\"single\" data-toggle-group-required> <button class=\"btn btn-outline\" aria-pressed=\"false\" value=\"s\">S</button> <button class=\"btn btn-outline\" aria-pressed=\"true\" value=\"m\">M</button> <button class=\"btn btn-outline\" aria-pressed=\"false\" value=\"l\">L</button> <button class=\"btn btn-outline\" aria-pressed=\"false\" value=\"xl\">XL</button> </div> <div class=\"toggle-group btn-group\" role=\"group\" aria-label=\"View\" data-toggle-group=\"single\" data-toggle-group-required> <button class=\"btn btn-secondary\" aria-pressed=\"true\" value=\"list\">List</button> <div class=\"btn-group-separator\" role=\"separator\"></div> <button class=\"btn btn-secondary\" aria-pressed=\"false\" value=\"board\">Board</button> </div>"
        },
        {
          "id": "vertical",
          "title": "Vertical",
          "text": "html <div class=\"toggle-group toggle-group-vertical\" role=\"group\" aria-label=\"Filters\" data-toggle-group=\"multiple\"> <button class=\"btn btn-toggle justify-start\" aria-pressed=\"true\" value=\"open\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m9 12 2 2 4-4\"/></svg> Open</button> <button class=\"btn btn-toggle justify-start\" aria-pressed=\"false\" value=\"closed\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m15 9-6 6M9 9l6 6\"/></svg> Closed</button> <button class=\"btn btn-toggle justify-start\" aria-pressed=\"false\" value=\"draft\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\"/><path d=\"M14 2v4a2 2 0 0 0 2 2h4M10 9H8M16 13H8M16 17H8\"/></svg> Draft</button> </div>"
        },
        {
          "id": "disabled",
          "title": "Disabled",
          "text": "Disabled buttons are skipped by clicks and arrow keys. html <div class=\"toggle-group\" role=\"group\" aria-label=\"Text formatting\" data-toggle-group=\"multiple\"> <button class=\"btn btn-toggle btn-icon\" aria-pressed=\"false\" value=\"bold\" aria-label=\"Bold\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8\"/></svg></button> <button class=\"btn btn-toggle btn-icon\" aria-pressed=\"false\" value=\"italic\" aria-label=\"Italic\" disabled><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M19 4h-9M14 20H5M15 4 9 20\"/></svg></button> <button class=\"btn btn-toggle btn-icon\" aria-pressed=\"false\" value=\"underline\" aria-label=\"Underline\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M6 4v6a6 6 0 0 0 12 0V4M4 20h16\"/></svg></button> </div>"
        },
        {
          "id": "htmx",
          "title": "With htmx",
          "text": "Buttons with a name send their value with the request they trigger, so each button can post the choice by itself: html <div class=\"toggle-group btn-group\" role=\"group\" aria-label=\"Theme\" data-toggle-group=\"single\" data-toggle-group-required> <button class=\"btn btn-outline\" aria-pressed=\"true\" name=\"theme\" value=\"light\" hx-post=\"/api/echo\" hx-target=\"#toggle-group-result\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41\"/></svg> Light</button> <button class=\"btn btn-outline\" aria-pressed=\"false\" name=\"theme\" value=\"dark\" hx-post=\"/api/echo\" hx-target=\"#toggle-group-result\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"/></svg> Dark</button> <button class=\"btn btn-outline\" aria-pressed=\"false\" name=\"theme\" value=\"system\" hx-post=\"/api/echo\" hx-target=\"#toggle-group-result\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"size-4\" aria-hidden=\"true\"><rect width=\"20\" height=\"14\" x=\"2\" y=\"3\" rx=\"2\"/><path d=\"M8 21h8M12 17v4\"/></svg> System</button> </div> <p id=\"toggle-group-result\" class=\"muted\" aria-live=\"polite\"></p>"
        },
        {
          "id": "reference",
          "title": "Reference",
          "text": "Class / attribute Description .toggle-group Spaced row of toggle buttons. Add .btn-group to join them. .toggle-group-vertical Stacks the buttons. data-toggle-group=\"single\" Pressing a button releases the others. data-toggle-group=\"multiple\" Buttons toggle independently. data-toggle-group-required With single: one button always stays pressed. toggle-group:change Event fired on the group; detail.value lists the pressed buttons' values."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/changelog",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/changelog",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/changelog.md",
      "title": "Changelog",
      "description": "Every release of HTMX UI, newest first. The project follows semantic versioning.",
      "section": "Project",
      "headings": [
        {
          "level": 2,
          "id": "versioning",
          "text": "Versioning"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "v0.1.0 Latest 2026-10-02 First release: design tokens, light and dark themes, twenty components including forms, toggles and loading states, syntax highlighting, versioned and agent-ready docs; plus htmx-ui-engine (dev server and builds on Bun or Node) and create-htmx-ui."
        },
        {
          "id": "versioning",
          "title": "Versioning",
          "text": "Versions follow semantic versioning. Before 1.0, minor versions (0.x.0) may rename classes or tokens; each release page lists what to change."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/changelog/0.1.0",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/changelog/0.1.0",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/v0.1/changelog/0.1.0.md",
      "title": "v0.1.0",
      "description": "First release: design tokens, light and dark themes, twenty components including forms, toggles and loading states, syntax highlighting, versioned and agent-ready docs; plus htmx-ui-engine (dev server and builds on Bun or Node) and create-htmx-ui.",
      "section": "Project",
      "headings": [
        {
          "level": 2,
          "id": "added",
          "text": "Added"
        },
        {
          "level": 3,
          "id": "components",
          "text": "Components"
        },
        {
          "level": 3,
          "id": "theming-and-dark-mode",
          "text": "Theming and dark mode"
        },
        {
          "level": 3,
          "id": "templates-and-data",
          "text": "Templates and data"
        },
        {
          "level": 3,
          "id": "docs",
          "text": "Docs"
        },
        {
          "level": 3,
          "id": "package",
          "text": "Package"
        },
        {
          "level": 3,
          "id": "engine-and-project-starter",
          "text": "Engine and project starter"
        },
        {
          "level": 3,
          "id": "for-ai-agents",
          "text": "For AI agents"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "All releases Latest 2026-10-02"
        },
        {
          "id": "added",
          "title": "Added",
          "text": ""
        },
        {
          "id": "components",
          "title": "Components",
          "text": "Accordion: built on <details>: one or several open, disabled items, bordered and separated styles, lazy-loaded content with htmx. Alert: neutral, info, success, warning and danger, with an optional dismiss button. Badge: solid, outline and soft status variants, plus a status dot. Button: primary, secondary, outline, ghost, danger and link variants; extra small to large sizes; icon, rounded, disabled and loading states. Button group: joins buttons, inputs, dropdowns and popovers: vertical, sized, separated, nested toolbars and split buttons. Card: header, title, description, action, content and footer parts; edge-to-edge images, horizontal layout, scrolling content, compact size and clickable cards. Code block: build-time syntax highlighting, copy button, file tabs and npm/pnpm/yarn/bun command tabs. Dropdown: accessible menu with keyboard navigation; powers the docs version switcher. Field: labels, descriptions and error, warning and success messages; an automatic required asterisk; inline, responsive and grid form layouts; fieldsets and separators. Icon: SVG files inlined at build time, or linked as images. Input: text inputs, textareas, selects and file inputs, with sizes and disabled, error, warning and success states. Input group: icons, text, badges, buttons, dropdowns and spinners inside an input, at the inline start or end, above or below. Popover: a floating panel of rich content or a small form that opens from a button. Sidebar: sticky on desktop, off-canvas with a backdrop on mobile. Spinner: any icon or a pure-CSS ring, in four sizes; works in buttons, badges, button groups and input groups, and as an htmx request indicator. Table: bordered wrapper, muted header and hover rows. Tabs: accessible tab groups that can sync with each other and remember the reader's choice. Text: @tailwindcss/typography (.prose) mapped onto the design tokens, plus heading, lead, muted, link, inline code and kbd helpers. Toggle: any button becomes an on/off toggle with aria-pres"
        },
        {
          "id": "theming-and-dark-mode",
          "title": "Theming and dark mode",
          "text": "Design tokens as CSS variables, exposed as Tailwind colours with @theme inline. See Theming. Thin, theme-tinted scrollbars everywhere (--scrollbar-thumb). See Theming. Class-based dark mode with a theme switcher that follows the OS until the user picks. See Dark mode."
        },
        {
          "id": "templates-and-data",
          "title": "Templates and data",
          "text": "Component behaviours initialise inside swapped content via htmx:after:process, including when the swapped element is the component itself. Optional Nunjucks macros for alerts, code blocks, command tabs, icons and spinners. json(), svg() and glob() template functions. See Templates & data."
        },
        {
          "id": "docs",
          "title": "Docs",
          "text": "Versioned docs: a version switcher, frozen snapshots of older versions under /docs/v<version>, and /docs/versions.json. See Versions. Site search (Ctrl / ⌘ + K): fuzzy-finds pages and sections in the docs version you're reading, and opens the exact section."
        },
        {
          "id": "package",
          "title": "Package",
          "text": "ES modules with TypeScript types for htmx-ui and htmx-ui/theme; CSS, icons and macros ship as source. htmx.org 4 and tailwindcss 4 are peer dependencies; @tailwindcss/typography is installed with the package. Nunjucks macros for alerts, code blocks, command tabs, icons and spinners, resolved by name in engine sites."
        },
        {
          "id": "engine-and-project-starter",
          "title": "Engine and project starter",
          "text": "htmx-ui-engine: htmx-ui dev, build and preview for sites made of Nunjucks pages, with file-based routing, Tailwind, hot reload (including layouts, partials and data) and mock htmx endpoints. Runs on Bun (Bun.serve, Bun.build) or Node (Vite), picked from whatever started it. See Engine. A Bun plugin (htmx-ui-engine/bun) and a Vite plugin (htmx-ui-engine/vite) for existing build setups. create-htmx-ui: npm create htmx-ui, pnpm create htmx-ui, yarn create htmx-ui or bun create htmx-ui scaffolds a site with pages, a layout, a theme toggle and an htmx demo."
        },
        {
          "id": "for-ai-agents",
          "title": "For AI agents",
          "text": "Every docs page as Markdown at <url>.md, plus /llms.txt, /llms-full.txt, /sitemap.json (with section text for search, and per-version sitemaps) and /sitemap.xml. See AI agents. Every docs heading has a # link, so sections can be shared and opened directly. Prompt examples and a ready-made agent skill that point at /llms.txt."
        }
      ]
    },
    {
      "url": "https://susonwaiba.github.io/htmx-ui/docs/versions",
      "absoluteUrl": "https://susonwaiba.github.io/htmx-ui/docs/versions",
      "markdown": "https://susonwaiba.github.io/htmx-ui/docs/versions.md",
      "title": "Versions",
      "description": "Docs for every release stay online. Use the version switcher at the top of the sidebar, or the links below.",
      "section": "Project",
      "headings": [
        {
          "level": 2,
          "id": "urls",
          "text": "URLs"
        },
        {
          "level": 2,
          "id": "switching",
          "text": "Switching versions"
        },
        {
          "level": 2,
          "id": "policy",
          "text": "Versioning policy"
        },
        {
          "level": 2,
          "id": "agents",
          "text": "For tools and agents"
        }
      ],
      "sections": [
        {
          "id": "",
          "title": "",
          "text": "Version Released Status Docs v0.1 2026-10-02 Latest /docs"
        },
        {
          "id": "urls",
          "title": "URLs",
          "text": "The latest docs live at /docs. Each older version keeps its own copy under /docs/v<version>: text /docs/components/button latest /docs/v0.1/components/button v0.1 /docs/v0.1/components/button.md v0.1 as Markdown Older versions are frozen exactly as they were published, including their styles and scripts, so their examples keep working after later releases change the components. They show a notice linking to the same page in the latest version, and ask search engines not to index them."
        },
        {
          "id": "switching",
          "title": "Switching versions",
          "text": "The switcher keeps you on the same page when it exists in the version you pick, and goes to that version's introduction otherwise. Older versions list newer releases too, because the list is read from /docs/versions.json when the page loads."
        },
        {
          "id": "policy",
          "title": "Versioning policy",
          "text": "Releases follow semantic versioning. Docs are versioned per minor release (0.1, 0.2, …) before 1.0, and per major release after. Patch releases update the docs of their version in place. The version under development has no number yet and is listed as next until it is released. Every release's changes and migration notes are in the changelog."
        },
        {
          "id": "agents",
          "title": "For tools and agents",
          "text": "/docs/versions.json lists every version with its URL prefix, its own sitemap (with section text, for search) and the pages it contains: /docs/versions.json { \"latest\": \"0.2\", \"versions\": [ { \"id\": \"0.2\", \"label\": \"v0.2\", \"path\": \"/docs\", \"latest\": true, \"sitemap\": \"/docs/sitemap.json\", \"pages\": [\"\", \"/installation\", \"…\"] }, { \"id\": \"0.1\", \"label\": \"v0.1\", \"path\": \"/docs/v0.1\", \"latest\": false, \"sitemap\": \"/docs/v0.1/sitemap.json\", \"pages\": [\"\", \"…\"] } ] }"
        }
      ]
    }
  ]
}