Use TagAction when you want plain semantic HTML to behave the way people expect (sortable tables, copy buttons on code, dialogs that open from a button) with one script tag, no classes, and no page script.
<script src="https://cdn.jsdelivr.net/npm/tagaction@0/tagaction.min.js" defer></script>TagAction is the companion to TagBrush.css, the classless stylesheet. TagBrush makes HTML look finished; TagAction makes it act finished. Each works without the other, and they look best together.
- Progressive enhancement. Every page works with JavaScript off. TagAction only adds behavior.
- No new syntax. It keys off native elements and attributes first, then ARIA attributes, then a few
data-tag-*attributes. No classes, no templating, no components, no reactive state. - Fills gaps, doesn't replace the browser. Native
<details>,<dialog>,popover, andcommandfordo their own work. - Works with htmx and anything else. Content added after load is enhanced automatically.
- Small. One file, no dependencies, about 9 KB gzipped.
| HTML | Behavior | Hook |
|---|---|---|
<dialog> |
A modal closes on a backdrop click. Opens from a button with no script. | native commandfor / command; closedby opts out |
<details> |
One open at a time; remembers open state; opens when a link points inside it; all open for printing; in the header nav, a dropdown that closes on Escape, a click outside, or a chosen link | native name; data-tag-remember (also keeps a table's sort order); #fragment links; <li><details> in a header <nav> |
<table>, lists |
Click a header to sort; columns of numbers line up on the right; filter from a search box (matches highlighted, with "No matches" and a count for screen readers; a name keeps it in the address); a list written hidden is search results |
<thead>; aria-sort; <input aria-controls> |
<form> |
Messages under the fields; busy submit button; unsaved-changes warning; a confirm field must match | native validation; method="post"; data-tag-match |
<textarea> |
Grows as you type; character counter; Ctrl+Enter or ⌘+Enter sends its form (with validation, never mid-IME) | no rows; maxlength |
<input type="file"> |
Lists chosen files, with thumbnails of pictures; drag and drop | multiple; accept="image/*"; its <label> |
<input type="password"> |
Show/Hide button | automatic |
<pre> |
Copy button | automatic |
<time> |
Reader's timezone; "3 hours ago" | datetime with a zone; data-tag-time="relative" |
<nav>, <a> |
aria-current="page"; follows the scroll in a table of contents |
automatic |
<h2>, <h3> |
"#" links; generated table of contents | id; <nav data-tag-toc hidden> |
<img> |
Click to enlarge; step through the pictures of a figure or section | a link to an image file; a shrunk image in <figure> |
<menu>, dropdowns |
Arrow keys, Escape, click outside; a popover of links closes when one is followed | <button aria-expanded aria-controls>; popover menus |
| Tabs | Shows the selected panel; arrow keys, Home, End; find-in-page and #panel links select a tab |
role="tablist", role="tab" with aria-controls |
| Selectable rows | A header checkbox selects all shown rows, goes mixed, Shift-click selects a range | <th><input type="checkbox"> |
| Shortcuts | A key focuses a field or clicks a button (not while typing, unless it has a modifier) | aria-keyshortcuts="/", "Control+K Meta+K" |
| Theme | Light/dark switch, remembered (load the script without defer so the saved scheme applies before the first paint) |
<select data-tag-theme> or <button data-tag-theme> |
| Copy, share | Copy any field or element; share a link or section | <button commandfor command="--copy">, command="--share" |
<output> |
Live value, total, or product; currency and percent formatting | native for; data-tag-calc="product", data-tag-format="USD" |
<abbr> |
Title shown on tap | title |
<template> |
Add repeated rows, and remove any of them | <button commandfor command="--clone">, command="--remove" |
llms.txt has the exact HTML for each one. It is the short, complete guide, written for people and for AI agents.
A modal with no script:
<button commandfor="confirm" command="show-modal">Delete</button>
<dialog id="confirm">
<form method="dialog">
<p>Delete this file?</p>
<button value="cancel">Cancel</button>
<button value="ok">Delete</button>
</form>
</dialog>A table you can sort and filter:
<input type="search" aria-label="Filter orders" aria-controls="orders" hidden>
<table id="orders">
<thead><tr><th>Customer</th><th>Total</th></tr></thead>
<tbody>
<tr><td>Maya Lin</td><td><data value="1240">$1,240.00</data></td></tr>
<tr><td>Bo Ito</td><td><data value="18">$18.00</data></td></tr>
</tbody>
</table>The filter box carries hidden because it only works with JavaScript; TagAction shows it. Any control that needs TagAction can do the same.
A dropdown that still works without JavaScript (the list simply stays open):
<button aria-expanded="false" aria-controls="account">Account</button>
<ul id="account">
<li><a href="/profile">Profile</a></li>
<li><a href="/logout">Sign out</a></li>
</ul>data-tagaction="off" on any element turns TagAction off for it and everything inside it. On <html> it turns TagAction off for the page. On a <th> it keeps that column from sorting.
A single MutationObserver enhances anything added to the page, including htmx swaps. To enhance content yourself (inside a shadow root, for example), call:
TagAction.init(element);init is safe to call any number of times on the same content.
Current Chrome, Edge, Firefox, and Safari. Where a browser lacks commandfor, exclusive <details name>, or CSS field-sizing, TagAction fills the gap.
TagAction 0.2.0 is tested with TagBrush.css 1.2.0 (0.1.0 was tested with 1.1.0). The unreleased changes are tested with TagBrush.css 1.2.0; tabs look best with the tab styles in the next TagBrush.css release.
npm install
npm run build # src/ → tagaction.js, tagaction.min.js, ai/ instruction files
npm test # Playwright: Chromium, Firefox, WebKitsrc/behaviors/has one module per behavior. Each exports an idempotentinit(root).llms.txtis the source of truth for agent docs;ai/is generated from it. Don't editai/by hand.- The tests check every behavior with mouse, keyboard, and content added later; the pages with JavaScript off; that
llms.txtonly documents what exists; contrast of everything TagAction inserts, in light and dark mode; and the size budget.
Apache-2.0