Project
v0.3.0
Six built-in themes with a switcher; htmx-ui-upgrade, one command that migrates a project between versions; clipboard, timeline and password input components; copy buttons that work everywhere; scroll-preserving htmx extensions; a dev server that reflects every change; and components rebuilt on shared utilities.
Breaking changes#
npx htmx-ui-upgrade@latest makes these edits for you, and lists what it
couldn't.
One change needs an edit, and only in hand-written code blocks; several component classes also moved onto shared button markup (see Components reuse each other).
Code block copy buttons are clipboard buttons#
initCode() is gone. The code(), code_block(), code_tabs() and
cli() macros render the new button for you. In hand-written blocks, replace the old button with an
icon-only clipboard button holding .clipboard-idle and
.clipboard-done icons:
-
Before:
<button class="btn btn-ghost btn-xs code-copy" data-copy>…</button> -
After:
<button type="button" class="btn btn-ghost btn-xs btn-icon clipboard code-copy" data-clipboard data-clipboard-target="pre:not([hidden]) code" aria-label="Copy code">…</button>
.code-copy-idle, .code-copied and data-copy-label are gone.
initComponents() runs the clipboard, so drop any direct initCode() call. See
Code block for the full markup.
Added#
Components#
-
Clipboard: copy buttons on any button variant and size, with an icon, a
label or both, that swap to a green tick for two seconds without changing width, and copy tags. It copies a fixed
text, the nearest element matching
data-clipboard-target(its value or text), or a URL's text (data-clipboard-url). It uses the Clipboard API with anexecCommandfallback for plain http, older Safari and iframes, is announced to screen readers, firesclipboard:copyandclipboard:error, and comes withclipboard()andclipboard_tag()macros. - Timeline: CSS only, with dot, icon, number or avatar markers in five colours and outline, a current step, opposite content, alternating sides, vertical or horizontal, dashed or dotted lines, and three sizes.
-
Password input: a show / hide toggle on an input group (an eye icon
or Show / Hide text, or a checkbox for several fields). It keeps the caret, hides the password again on submit so
password managers save it, hides Edge's own reveal button, has an optional Caps Lock warning, and a
password_input()macro.
Scroll position across swaps#
-
preserve-scroll, an htmx extension:
registerPreserveScroll(), thenhx-preserve-scroll="<key>"on any number of scroll containers keeps their position when htmx replaces them, boosted navigations included. Repeated keys match in order;snapshotScroll()andrestoreScroll()do the same for DOM you replace yourself. -
sidebar-active-scroll, for navigation sidebars:
hx-sidebar-active-scrollkeeps the position across swaps and the current page's link in view, centred on a fresh page load (a link opened in a new tab, a reload) and scrolled just into view after a swap that hid it. The docs sidebar uses it.
Copying everywhere#
- Every copy button is now a clipboard button that shows a green tick for two seconds: icon only on code blocks and the docs demos, icon and text on the docs plugin's "Copy Markdown" button.
-
"Copy Markdown" reads "Copied" after copying and fetches the page's
.mdthroughdata-clipboard-url, so it needs no plugin client.initMarkdownCopy()fromhtmx-ui-plugin-docs/clientstays, deprecated, for pages built with the older markup. - Copying from a code block now works over plain http too.
Themes#
-
Six built-in themes as
htmx-ui/themes/<name>.css: aurora (violet→indigo→cyan — the one this site runs on), ember (amber→orange→rose on warm paper), lagoon (teal→cyan→sky), dusk (pink→fuchsia→purple), jade (emerald→teal→lime) and onyx (monochrome graphite and silver). Each redefines the brand tokens —--radius,--primary,--ring,--brand-gradient— plus dark--background,--surfaceand--border. Opt-in both ways: no import, no CSS; no attribute, no styles. A switcher ships with them — buttons carryingdata-theme-paletteapply their palette and remember the choice (initTheme()wires them;getPalette()/setPalette()are exported) and thetheme-swatchdot draws the current one. See Theming for the mechanism and Themes for the catalogue — every theme previewed, its exact palette, a one-click apply.
Docs#
- Input OTP is documented for visible 4-digit authenticator codes and PINs, next to 6-digit codes.
Changed#
The select() macro covers what the component does#
The docs use it wherever they had a native <select> (field, button group, pagination, prompt
input). New options:
-
idon the trigger (for<label for>),labelledby,describedby,size,align,side,invalid,requiredandclass. -
Option groups (
{label, options}) and per-optiondisabled,iconandkeywords.attrsalso takes a dict. -
The preselected
valueis written into the trigger and the hidden field, so it shows and submits before JavaScript runs.
The select's search now filters like the combobox's (accents, every
word, data-keywords, empty groups hidden), and typing a letter in a list without a search box jumps to
the next option starting with it. Button groups and .prompt-input-select accept the select component as
well as a native select.
Components reuse each other#
-
Shared CSS: shared looks are Tailwind utilities in the new
utilities.css:menu-item,menu-label,menu-separator,menu-empty,popup-surface,focus-outline,input-bare,label-text,description-text,choice-card,panel-title,chevron-mask,summary-triggeranddetails-animate. Select, combobox, command, dropdown, prompt input, popover, hover card, navigation menu, label and field, the radio card, accordion, reasoning and chain of thought are built on them. -
Button markup: close buttons (dialog, sheet, alert), the code block's copy button, the sidebar
trigger and the message scroller's jump button are
.btnbuttons..dialog-close,.sheet-close,.alert-close,.code-copy,.sidebar-triggerand.message-scroller-jumpnow only position them. Addbtn btn-ghost btn-icon btn-sm(copy:btn btn-ghost btn-xs; jump:btn btn-outline btn-sm btn-rounded) to hand-written markup. This also gives the alert's close and the copy button a focus ring. -
Select trigger:
.select-triggershares the.inputrules, including.input-warningand.input-success. -
Code tabs: code block tabs are
.tabs-list.tabs-line.code-tabswith.tabs-triggerbuttons..code-tabis gone. - Item separator:
.item-separatoris gone; use.separator. -
Reasoning and typing:
.message-reasoningand.message-typingare gone; use the reasoning component andloader("typing").reasoning()renders its streaming label withloader(), andloader(…, label=none)on a text variant dropsrole="status". - Toast icons come from
src/icons.
Shared behaviour helpers#
New modules in packages/ui/src/utils/:
-
listbox.ts: option filtering and theactiveDescendant()highlight, used by combobox, command and prompt input, and filtering in select. dismiss.ts: closing on an outside pointer down or when focus leaves.position.ts:place(), which flips a popup that would leave the viewport.shared.ts: ids, arrow-key indexes, small checks.hotkey.ts:matchesHotkey, still exported fromhtmx-ui.
As a result:
- Select, combobox and popover lists flip when they would leave the viewport, and so does the dropdown's own menu (before, only submenus did).
- Tabs and toggle groups swap ← and → in right-to-left layouts.
- The command menu's filter ignores accents.
- The listboxes scroll their own list, never the page.
Also changed#
-
dismissiblereacts to any[data-dismiss]inside it, including buttons added later, and fires a cancelabledismissible:dismiss. It setsdata-state="closing"and waits for a transition before removing the element. -
Macro options are consistent. Every component macro takes
class=""andattrsas a dict, throughmacros/attrs.html; stringattrsstill work.alert,dialog,drawer,sheet,empty,code_block,code_tabs,cli,checkboxandinput_otpgained the options they lacked, andcheckboxgainedinvalid. -
A new
--overlaytoken (bg-overlay) dims the page behind modals, sheets, drawers and the mobile sidebar. Accordion, reasoning, chain of thought, collapsible, progress, toast and sidebar transitions use the motion tokens, so they stop under reduced motion. -
htmx-ui-plugin-search's palette is htmx-ui's command menu in a dialog
(
.dialog.command-dialog,.command-*); its stylesheet only sizes it..search-field,.search-input,.search-groupand.search-messageare gone. - htmx-ui-plugin-versions' old-version banner shows the alert's warning icon.
Fixed#
-
Switching docs versions loads the target page instead of swapping the body under
the version you are leaving. Version links carry
hx-boost="false"— the switcher's items, the ones its client rebuilds from the manifest, the old-version banner, and the links on the versions page — because every version brings its own theme, styles and scripts. Archiving bakes it into a snapshot, whose<body>carrieshx-boost="false"as well: nothing inside a frozen page (its logo, footer, sidebar or content links) may boost the live site's code into it. The published v0.1 and v0.2 snapshots carry it all, andhtmx-ui-upgradeadds the attribute to a hand-written switcher. -
Pages your server renders get their scripts and styles. A page from
site.render()(or fromhandle()in development) linked its sources, such as/app.ts, which nothing served. In development they are now bundled from source on request, kept in memory and reloaded on change, never written todist/; in production they link the build's bundles, from the manifesthtmx-ui buildnow writes. See Development and production. -
NODE_ENV=productionworks on Node: the engine's compiled code had the value it was built with baked in, so the adapters on Node always behaved as in development. - The Express, Fastify and Koa adapters stream responses, so one that never ends (server-sent events) reaches the client.
-
Server adapters in development show template edits on the next request, find pages added since the server
started, and serve
public/before a build has copied it. -
htmx-ui devon Bun shows every change without a restart. Edits to a linked package (a workspace package,file:orlink:dependency) were picked up once and then missed, and saves that replace the file were always missed; stylesheets that a Tailwind stylesheet@imports now reload the page; changes tohtmx-ui.config.ts, plugins, the mock API or the engine now reach rendered pages; and a new page gets its route without a restart. If Bun's dev server crashes,htmx-ui devstarts it again and open pages reload. See watching. -
Carousel previous and next buttons respond to clicks again. A pressed
.btnnudged down withtranslate-y-px, which replaced the buttons' own centring, so they jumped out from under the pointer. The press nudge is now atransform, which adds totranslate-*positioning. New.btn-no-pressturns the nudge off. -
Scroll areas inside components show their scrollbar without hovering: popup lists and menus (select, combobox,
dropdown, command, popover…),
.command-listand.card-scrolluse--scrollbar-thumbthrough the newscrollbar-visibleutility, so a long list looks scrollable. Other scrollbars still appear on hover. -
The page no longer shifts sideways when a modal dialog, sheet or drawer opens:
htmlreserves its scrollbar's room (scrollbar-gutter: stable), so hiding the scrollbar to lock the page doesn't widen it.