What's New
Release history and changelog for the Domma framework.
Five fixes: a range slider returns to its starting value on form reset and no longer picks up a stray tooltip, a wizard keeps what was typed when you go back a step, .html() keeps table rows written into a table body, and an overlay's focus trap no longer treats a fixed page header as a floating layer. Kickstart projects now get the generated TypeScript declarations.
The APIs deprecated in 0.55 are removed: the modal's, popover's and sortable's old unprefixed events, the array shape of E.tooltip, E.buttonGroup and E.listGroup, E.timeline() and the old theme variant methods. Each has a direct replacement listed in the release notes.
Modals, slideovers, dialogs and popovers now keep Tab inside while open and hand focus back when they close, stacking cleanly when one opens another. The TypeScript declarations are generated from the source, so they match the code and cover the router, auth, forms and every E.* component.
Seven new pieces built to the 0.55 conventions: a dropzone with upload progress, a single or two-thumb range slider (both also as form blueprint fields), a Cmd/Ctrl+K command palette, a focus trap, resizable split panes, a virtual list that scrolls 100,000 rows with infinite loading, and Domma.i18n translations with plurals, Intl formatting and right-to-left support.
One implementation per component behind E.* - theme colours finally reach modal, badge, toast and the rest, and nothing replaces your element - plus one event, verb and lookup convention: .on() and dm:* events for every callback, open/close on every overlay, a cancelable beforeclose, and E.get() for everything.
Quote-safe escaping everywhere, sanitised card content, Node-safe imports, Domma.noConflict(), router and HTTP fixes, and an install that runs no scripts.
Fresh seasonal artwork: a new St George's castle, 26 illustrated details and Patron Saints' Day particles spread across the scene from the first frame.
Scribe's fade, bounce and glow now follow the typing and delete speeds, fading text out as well as in.
Fix the disappearing email-body caret when a rich editor is assembled before its compose window opens.
Smoother typing in forms and editors. Text inputs, textareas and rich-text editors share a caret glide, with Automatic, Smooth and Native choices. Dynamic wizard steps and dialogs are covered, while password fields and native picker controls retain their usual behaviour.
Twenty celebration scenes, custom sparkler messages, cursive gold and silver lettering, garden visitors, real rain and personal occasions.
Dialogs no longer answer themselves when opened with the Enter key, Escape closes only the top-most dialog or slideover, and danger: true makes a confirm start on Cancel.
The router keeps up with fast navigation: a link, Back or Forward pressed while a screen is still changing now shows the right screen instead of leaving the old one.
Required dropdowns in generated forms start on a placeholder, so the first option is never shown as picked when nothing has been chosen.
Carousel images fill one consistent viewport from the top. Choose the shortest scaled image, the midpoint of the shortest and tallest, or a fixed height.
The CSS fireworks animate again - launch, sparkle and explosion.
The modal footer now follows the theme, so dark themes no longer show a white bar at the bottom of dialogs.
Bundles domma-reactive 1.3.1: names with &, < or quotes now read correctly in the tooltips and screen-reader labels of reactive list rows.
Scribe keeps whole words together when animated text wraps, including bounce effects and narrow containers.
The centred timeline now stacks neatly on phones.
Smoother Scribe animations, reliable pause and resume, and intact emoji.
Modals made with createModal() / showModal() show their HTML content and buttons again.
Context menu items take their own CSS classes through a new className option.
Curled ticker tape with shaded folds and consistent motion, plus illustrated Bonfire Night decorations, leprechauns, bagpiper, knight, dragons and Cupid.
Butterflies now have tapered wings, fine veins and markings, antennae and a gentler illustrated finish.
The Christmas snowman now matches the illustrated trees, wreaths, robins, elves, sleigh and train, with shaded snow, a knitted scarf and a holly-trimmed top hat.
Christmas gets illustrated trees, wreaths, robins, elves, Santa's sleigh and a vintage express. Two optional naughty elves add a little mischief; both are off by default.
Cards and boxes change only their border colour on hover, transitioning smoothly from their existing colour to the active theme colour.
Cards and boxes respond to hover with a subtle outline in the active theme colour instead of lifting off the page.
Built-in array helpers for binding expressions: len, includes, count, where, sortBy and more - no computed needed to ask whether a list is empty or how many rows are done.
Readable everywhere. A contrast sweep moves buttons, heroes, footers and form controls onto the theme's own text colours, so text stays readable on every theme, light and dark.
One clear warning. Domma Reactive 1.2.1 names every binding an unkeyed list dropped in a single message, and points at key= - the fix that makes them bind.
Accordions open again. Accordions whose answer sits inside a body and content pair - the markup Domma CMS renders - now open to the right panel instead of an empty one.
Safer bindings. Domma Reactive 1.2 warns and hides content when a binding is handed an observable instead of its value, and expressions accept object and array literals.
Avatars. E.avatar() shows a person as their picture or their initials, in a colour that stays the same wherever their name appears and readable on every theme; E.avatarGroup() stacks a team with a "+N" that lists the rest.
Plus accessibility and text utilities: .sr-only, a ready-made .skip-link, .line-clamp-*, .stretched-link for whole-card links and .divider-text.
Popovers. E.popover() anchors a panel with a title, text or a small form to a trigger - opened by click, hover or focus, flipped and shifted to stay on screen, with focus moving in and back out for keyboard users. data-popover markup and E.popover.scan() do the same without script.
Skeleton loaders. Shimmering, theme-aware placeholders shaped like the content on its way: CSS shapes for plain HTML, E.skeleton() for text, card, list and table layouts, E.skeleton.while() to show one until a promise settles, and loadingSkeleton rows for the DataTable.
Plus an accessibility sweep: Modal, Tabs, Accordion, Toast and Table add their own ARIA roles and keyboard support, and 31 theme contrast failures are fixed.
Four interactive extras for input groups: a show / hide password button, a clear button, a number stepper that honours min, max and step, and a live character counter - joined to the field like an addon, keyboard reachable and screen-reader friendly.
The same keys work in a blueprint's formConfig and in E.inputGroup():
reveal, clear, stepper and counter.
Links now read at 5:1 or better on every theme: the 25 whose main colour was too pale set a readable link colour.
A Forms icon set: eighteen new icons for the side of a form field - email, name, phone, address, postcode, website, pound, dollar, euro, weight and more - plus a Forms category that gathers every icon suited to a field.
Input addons now join the field they belong to: formConfig.prefix and suffix draw as one Bootstrap-style group with a shared border, sized to what they hold, in the theme's own colours - and they work on selects and textareas too.
New E.inputGroup(selector, {prefix, suffix}) does the same for an input already on the page, with update() and destroy().
E.sortable(selector, options) is drag-to-reorder for the children of one container, by mouse, touch and keyboard. In live mode the other items slide out of the way while you drag; with nest: true nothing moves until the drop, a marker shows whether the item lands before, after or into another, and onDrop hands you the move to update your data and re-render - which the component then animates, matching every item by its key. That is what makes it suit trees, and the Domma CMS menu editor now uses it.
An order that sticks. persist remembers the order through Domma storage and puts it back on the next visit. Alt+Arrow moves the focused item, Esc cancels a drag, a handle drags at once on touch while a whole item needs a short press-and-hold so the list still scrolls, and reduced motion skips every glide.
Installing domma-js no longer installs a development web server. live-server was an optional dependency, so every project using domma-js pulled it in with about 180 more packages; it is now a dev dependency only, and domma-js serve still offers to install it when first run.
Every left-hand menu now takes its colours from the active theme. A new theme sidebar variant follows the theme's --dm-sidebar-* tokens, light or dark, and the showcase, admin and miniapp sidebars all use it.
The Admin themes' dark sidebars get washes that show, sunset-dark and grayve-dark selections are readable again, and the What's New pill stops pulsing once you have seen the latest release.
Domma is now built against domma-reactive 1.1.1.
Five new themes arrive in Domma: Corporate (light and dark) and Admin Slate (steel, indigo, teal). Corporate introduces an executive navy aesthetic tailored for business dashboards and reporting, while Admin Slate expands the administrative theme matrix to 3ร3 with a balanced mid-tone finish designed for high visibility and long work sessions.
Full tooling support: all five themes are integrated into the Theme Roller, Kickstart builder, TypeScript declarations, and showcase previews with verified WCAG contrast compliance.
Context-menu items now highlight on hover in every theme. The old wash matched the menu surface in every dark theme, so it was invisible; it is now mixed from the theme's text colour, a submenu's parent stays highlighted while the submenu is open, and --dm-ctx-hover overrides it.
No change in behaviour. Domma is now built against domma-reactive 1.1.0, whose only change is that the standalone package ships TypeScript declarations. Domma inlines the engine, so its own API is untouched and nothing needs changing when upgrading.
A computed value could stop updating some of its readers. A computed reading another computed and the source that one was built from refreshed the inner one early, so when the change reached it the inner computed looked unchanged and its other readers kept the old value.
domma-reactive 1.0.2 remembers the early refresh and reports it, so every binding, effect and computed that reads it updates. No code changes are needed.
A context-menu submenu could stay on screen for good. Hovering another item left an open submenu under the pointer, and opening a second submenu orphaned the first in the document, where not even closing the menu could remove it.
Each menu now closes its own submenu. Hovering an item, or opening a submenu, closes from the menu's own depth rather than the one below it, so a sibling's panel is always closed before its slot is reused. Nested submenus are unaffected.
Pillbox pills were never styled. The stylesheet styled .dm-pillbox-pill, .dm-pillbox-remove and .dm-pillbox-message; the script emits .dm-pill, .dm-pill-remove and .dm-pillbox-empty, the names the showcase has always documented. Each pill arrived as bare text beside a grey browser button.
The rules now match the markup, including the size variants, a :focus-within focus ring, an unbulleted options list and a disabled state.
The colours follow the theme. The --dm-pill-* and --dm-pillbox-* defaults are fallbacks in the rules rather than :root declarations, so they resolve against the theme on <body>; pills default to --dm-primary-light, which every theme defines.
$('#list').append($row) did not put $row in the page. It put a copy there. Every insertion method went through one helper that returned cloneNode(true) of whatever it was handed, so the node you built, held a reference to and bound handlers on was never the node the visitor saw. The markup was right, nothing threw, and the only symptom was that your listeners stopped existing.
One helper, five methods. _getNodes() backed append(), prepend(), after(), before() and replaceWith(), and appendTo() and friends through them. cloneNode copies attributes and descendants; it copies neither listeners nor properties. So a handler bound before the insert fired on a detached node nobody could click, and prop('checked', true) was dropped while attr('checked') survived - the two quietly disagreeing depending on which you happened to use. $el[0] kept pointing at the original for the rest of its life, so the element that actually landed in the document could only be reached by querying for it again. An existing node is now moved, which is what jQuery does and what the call site assumes.
Except when a node has to be in two places. $('.card').append($badge) has three targets and one badge. The extras are still clones; the original goes to the last target, as in jQuery. Those clones carry no listeners and cannot: Domma records delegated handlers on the element but not direct ones, so there is nothing to re-bind. Inserting into several targets at once still wants a delegated handler on a container. A markup string is unaffected - it was always parsed fresh per target.
The celebrations are their own package now. The eight seasonal themes move to domma-celebrate, so a site with no Domma in it can use them; 14,700 lines leave public/layouts/js/modules/celebrations/ and nothing in the npm package changes. The build is code-split - the engine is 31 KB and each theme is a chunk fetched only when it is in season, rather than all eight arriving in December and in July alike. Every decoration is individually configurable: snow but no steam train, pumpkins but no witches, half as many trees, via traits: { train: false, tree: 0.5 } or setTrait() at runtime. public/layouts/js/modules/celebrations/ and templates/celebrations-toggle.html are gone, which matters only if you copied the layout preset system by hand.
How it was found: the celebrations demo grew per-trait checkboxes, and they ticked and did nothing. All on and All off worked, because those are static buttons bound by id and were never cloned - which is what made it look like a celebrations bug for as long as it did.
Not caught by anything: no test failed, and none of the four validators can see this. There are nine tests now, six of which fail against the previous implementation, and 605 pass overall including the 85-page showcase harness.
Context menus, and the nesting problem underneath them. E.contextMenu(selector, options) binds a right-click menu to a container and, by delegation, to everything inside it. match: 'tr[data-id]' picks the row and hands it to your items, so one binding covers a whole table - including rows rendered after the menu was declared, which a per-element binding loses on the next repaint.
Menus nest without overriding each other. Bind one inside another and the inner one shadows its parent for the region it covers; everywhere else the parent still answers. By default the parent's items are appended beneath the child's, so an entry menu inside a page menu offers both. A menu declines rather than swallowing a click it has nothing for - enabled false, an exclude match, a match that finds nothing - and the walk continues outward to the next menu up. Nothing claims it, and the browser's own menu opens untouched, as it does on Shift+right-click.
One document listener, not one per instance. That is the point rather than an optimisation: an instance owning its own listener makes the winner depend on which script registered first, so a menu can shadow its own parent purely on bundle order - intermittently, and differently in dev and production. Resolution is a walk outward from the event target, so DOM depth decides and load order cannot. Two of the 37 tests assert the same nesting with the outer menu registered first and then registered last. Nothing calls stopPropagation(), which would silence every other contextmenu listener on the page.
Reachable without a mouse. Right-click has no keyboard equivalent, so Shift+F10 and the Menu key open the menu against the focused element, then arrows, typeahead, Enter and Esc. Submenus nest to any depth, and a long press opens the same menu on touch. Building it turned up color: var(--dm-danger) on --dm-surface at 1.02:1 on admin-smooth-steel - "Delete" in red would have been invisible on every mid-tone theme. validate:contrast caught it before it shipped.
A region can refuse to be taken over. Depth deciding is wrong for a component that owns its region - a data grid with its own filter panel, an editor, a canvas - because any application menu bound inside it wins by being deeper. exclusive: true inverts the rule for that one menu: once it encloses the click and accepts it, nothing bound deeper is offered the gesture. It is not a veto, since an exclusive menu that declines steps aside completely. Declaring it on the menu being protected is the point - a guard on every menu that might collide protects nothing the first time a registration path forgets one.
Colour, surface and motion are configurable. accent (a preset key or any CSS colour), surface, radius, shadow, opacity and density, plus transition (scale/fade/slide/none), easing and animationDuration. Every one is a custom property with the theme token as its fallback, so a menu given no styling follows the active theme and only what you pass is overridden. Two carry a deliberate restriction: the accent tints the border and the hover wash but never the label, because a named colour cannot be guaranteed readable against whatever surface the menu lands on; and opacity mixes the background with transparent rather than fading the element, which would take the text down with it.
Every menu in Domma is separated from the thing that opens it. The dropdown renders on document.body, so offset px of page always sits between the two; the navbar's panel hangs 4px below its toggle. That gap is the bug: leaving the trigger looks exactly like leaving for good, so both components guessed, and both guessed wrong most of the time.
A gap you can only cross by accident. The dropdown bridged it with a 12px transparent CSS strip above the menu. Straight down, it worked. Diagonally, with an offset above 12px, or with the menu placed above or beside its trigger, the pointer went round the strip and the menu shut - whether it survived came down to how fast you moved. The pointer is now tested against three regions: the trigger, the menu, and the corridor spanning the gap. That is geometry rather than guesswork, so it holds for any offset, any angle of approach, and a menu flipped to the other side. The navbar had the same bug one level up, and its grace period is now an option, hoverCloseDelay, defaulting to 250ms.
A click that meant nothing. A navbar toggle did a plain classList.toggle('open'), which the next mousemove reconciled straight back when appearOnHover was on - so clicking a menu to keep it open did nothing visible. A click now pins the dropdown; a second click, or one anywhere outside, releases it. A toggle also left its siblings open, so two navbar menus could sit on screen at once.
Four more, found alongside. The dropdown trigger called stopPropagation(), which killed every other dropdown's outside-click handler, so opening a second menu left the first hanging open. close() left the node live and clickable for the whole fade, so re-opening inside that window stacked a second node on top with its own listeners. position: 'left-start' and 'right-start' silently fell through to bottom - the showcase's own Left and Right buttons were wrong; all eight side/alignment combinations work now, and a menu that would open off screen flips or clamps. And a hover dropdown had no click path, so it could not be opened on a touch device at all.
One default moved. Navbar.collapseAt was 768 while elements.css switches the bar to its desktop layout at 993. Between the two the JS believed it was on desktop while the stylesheet was already showing the mobile drawer. The default is now 993; pass collapseAt explicitly if you have restyled the breakpoint.
Also: Esc closes a dropdown, the trigger carries aria-haspopup and a tracked aria-expanded, and per-item click listeners are no longer re-registered on every render and never released. New options: hoverOpenDelay, hoverCloseDelay, closeOnEscape, closeOthers, flip. None of this failed a test, because there were no dropdown tests - there are ten now, plus three for the navbar; eight of the ten fail against the old implementation.
A navbar with variant: dark rendered its brand at 1.00 contrast on every dark theme. Same colour as the bar it sat on - not dim, not low-contrast, absent. CSS only; no API changes.
One mistake, four rules. .navbar-dark painted the bar from --dm-background and its text from --dm-text-inverse. In a dark theme both resolve to the theme's darkest slate, so the two are the same colour; .navbar-light failed the mirror way in light themes, a white bar with white text. --dm-text-inverse means text that contrasts against --dm-text, so in a dark theme it is dark - it reads like the right name at the call site and is the wrong one, which is why the same mistake is in three more places.
.card-primary .card-body took --dm-primary-text, the foreground for text drawn on a primary fill. The header and footer set that fill; the body does not, it sits on the card surface. 1.19 on every dark theme. .navbar-link.active took --dm-primary as its text colour, and primary is a mid-tone in the silver, charcoal and lemon families - against the navbar surface it measured as low as 1.72. The .navbar-dark .navbar-dropdown-* overrides replaced the panel text with a fixed grey and the hover with --dm-text-inverse over 30% white: 2.56 and 1.00 in every light theme. They only ever re-stated the --dm-surface background the base rules already set, so they are deleted rather than rewritten - and the divider among them was painted --dm-surface, the panel's own colour, so it had been invisible since the day it was written.
The variants are now relative to the theme, not absolute. Every theme already ships the pair meant for this: --dm-navbar-bg and --dm-navbar-text. The variants become tint modifiers on that surface - dark sits a shade below the theme's navbar, light a shade above. They stay visually distinct, they follow the theme, and neither can collide with its own foreground. Measured with computed styles in headless Chrome across 26 themes and three variants: navbar and card worst case 1.00 to 5.00, dropdown worst case 1.00 to 9.57, everything clear of the 4.5 AA threshold.
Two visible changes to live sites. The active link loses its pill background: a --dm-primary tint shifts the bar toward the text in themes where primary is light, costing about two points of contrast on the tightest combination, a dark theme with .navbar-light. The affordance is now the primary underline plus the weight, which means the active state can never read worse than the brand beside it. Dropdown panels under a .navbar-dark bar follow the theme now instead of being fixed grey on a surface that was already correct.
Not fixed: validate:theme caught none of this. It looks for a fixed background inheriting themed text, and cannot see two themed tokens that happen to resolve to the same value. The validator that would have caught all four does not exist yet.
The inlined binding engine moves from 0.5.1 to 1.0.0, and brings two context names with it. No new Domma API of its own; nothing that already worked has changed.
$parents is the whole ancestor chain, nearest first. $parent is one step out, and until now it was the only step there was - from inside a list inside a list, two levels up could not be reached at all: a bare name is the item, $parent is the group, and the root was simply unreachable. $parents[0] is $parent and $parents[1] reads a grandparent with no $data in sight. It is built only if a template asks for it, by walking the chain on first read, so a list that never mentions the name pays nothing for it.
$parentContext is the enclosing context rather than its data - the one name here that is a context. It exists because ancestor data cannot answer which row of the outer list am I in?: position lives on the context, not on the item, so no amount of $parents reaches it. $parentContext.$index answers it, and nothing could before. $parent stays data and not a context, because making it one would mean writing $parent.$data.name everywhere.
A list inside a list is {{#each}}, not a second data-each. Always true, and it never said anything. data-each is the applyBindings spelling and the compiler discovers lists from {{#each}} only - and a list's item template is compiled markup. So a nested data-each was left exactly as written: its body rendered once, as ordinary markup, with every binding in it resolving against the outer item. It looks close enough to working to survive review, which is how it survived. Activation now warns, quotes the expression back and names the form to write instead.
Also arriving with the pin: a binding write to a frozen target warns and skips instead of throwing. It was reachable long before either name existed - data-model against a frozen view model has always landed there - and a binding that throws takes the page down with it, which is the one thing this layer promises never to do.
Known limitation - $parentContext.$index does not survive a reorder. Appending to the outer list is fine, and so is anything that leaves existing positions alone. Moving an outer row is not: a sort, a reverse or a prepend relocates the DOM correctly and leaves every inner item reading the position its group used to be in. Ancestor data is correct throughout; it is only the enclosing position, and only after a move. It is documented next to the feature rather than left to be discovered, and the showcase demo appends rather than reorders for exactly that reason.
Internal: domma-reactive moves from 0.5.1 to 1.0.0 - the same code as 0.8.0 with a promise attached: thirty-three exported names are the public API, the binding spellings are settled, and a breaking change needs a major version. 961 tests there, up from 793. Components and slots are not part of this: Domma has its own web-component factory and never imports registerComponent, so that half of the package is tree-shaken out rather than bundled and dormant. 579 tests here, up from 577.
A carousel whose slides are text rather than pictures did not appear at all. One CSS rule; no API changes.
.carousel-slide-content was unconditionally position: absolute - it is the caption overlay for a slide's background image. That works when the slide carries an <img>. When it does not, the caption was the slide's only child, so nothing was left in flow: the slide computed to 0px tall, the track and the carousel with it, and the component rendered as a gap. Arrows, indicators and transitions were all working the whole time, on an element with no height.
Slide content now sits in flow by default, and the overlay is stated as the enhancement for .carousel-slide:has(> img). In-flow content is also padded clear of the arrows and indicator dots.
Forma's option groups get a real appearance, and the chooser stops squeezing itself onto phones. Two visual fixes and one spacing fix; no API changes.
Radio and checkbox groups are styled. type: 'radio' and type: 'checkbox-group' rendered bare browser controls: the inputs carried no class at all, and there was no CSS anywhere for .domma-radio-group or .domma-checkbox-group. They now use .form-check-input - the same custom control .form-check has always had - and each option is a selectable tile: the whole label is the hit area, the checked state tints from --dm-primary, and the group wraps with repeat(auto-fit, minmax(12rem, 1fr)), dropping to one column below 480px.
The card chooser steps down on narrow screens. .picker-options used repeat(var(--picker-cols, 3), 1fr) at every width, so a three-across grid stayed three-across on a 360px phone. The chooser now sets --picker-cols-md and --picker-cols-sm when it builds, both capped at the configured count, and steps down at 720px and 460px. A chooser configured with one or two columns is never widened.
Dialog footers also give the confirm button room to breathe.
Six new binding attributes and two new reactive powers, all of them things you previously had to write yourself. Nothing that already worked has changed.
data-options fills a <select> from a collection, with data-options-text, data-options-value and data-options-caption. A data-each over <option> produces the same markup - what it does not produce is the selection, and that is the whole difficulty: rebuilding a select's options resets it, and the selection lives on the select rather than on any option, so a keyed list has nothing to preserve it with. Option values need not be strings: when the resolved value is an object or a number, the real value is kept alongside the option and data-model reads back that, not "[object Object]". And a value the model asked for while no option carried it is remembered and applied by the rebuild that brings it - so attribute order does not matter, and neither does a collection that arrives from an H.get() long afterwards.
data-focus is two-way between a value and focus - how a model puts the caret in the field it has just revealed without reaching for a DOM node, and how it knows where the user is without a listener. Unlike data-model, an expression it cannot write through is not fatal: focus still follows the value and only the write-back warns.
data-bind-style-<property> and data-bind-style set one CSS property or a whole object of them. Two spellings because a binding expression has no object literal and will not grow one - parsing {โฆ} safely is most of the way to the eval that Domma's Content Security Policy story depends on avoiding. Ownership follows the class rule, so a static style= attribute survives; a falsy value removes the property, and 0 is kept, because opacity: 0 is a real value.
Virtual bindings - <!-- dm if: x --> โฆ <!-- /dm -->, plus each and text. A binding attribute needs an element to sit on, and sometimes there is none to spare: a run of <li>s, three <td>s in a row. Wrapping them in a <div> changes the layout, and inside a table it is not even valid HTML a browser will keep.
M.observable(x).extend({โฆ}) layers behaviour onto an observable after it exists: rateLimit, throttle and notify: 'always', with M.registerExtender() opening the same registry to you. The write is never delayed, only the announcement - worth stating plainly, because the obvious implementation of a throttle defers the write, and then every read between the write and the notification gives you a value that is already out of date.
M.computed({read, write}) is a computed you can write through, which is what lets data-model="fahrenheit.value" bind a derived value. Assigning to one with no write now warns and names it, rather than storing into the read cache where the next recompute would silently drop it. M.observableArray() gains indexOf(), replace() and destroy() / destroyAll(), the last of which marks an item rather than removing it - for servers that delete on a flag in the payload.
Internal: domma-reactive moves to 0.5.1 - 793 tests there, up from 679, and a step-by-step tutorial whose every listing is under test. All six bindings arrive with the pin, since the handler registry populates itself at import; what needed Domma code was the other two, reachable in the package and not through M. 577 tests here, up from 571.
No new API. This release is the seam between Domma and the binding engine, made single - plus a limitation found by measuring rather than assuming, and now written down.
One binding adapter, shared. The engine resolves every expression against one object and writes back through that same object. Getting either half wrong fails silently: a read-only snapshot swallows every data-model write - the control looks right, because what you see while typing is your own keystrokes - and an object carrying no functions resolves no data-on-* handler while every other binding on the same element works perfectly. Both shipped, and were fixed in v0.37.0; but the fix then existed twice, in two slightly different shapes. It is now once, in src/binding-source.js, with the failure modes documented where the code is rather than in two comment blocks that drift apart.
Writing to a nested path does not notify the model. data-model="profile.city", where profile is a Model field holding an object, reads correctly and the write lands - but it mutates that object in place, so the field's observable never fires: the model is not told, and no other binding on that path updates. This was found while measuring whether M.bind()'s parse option could be retired. It cannot - parse is currently the only way to write a nested path and have the model notice, because it clones the field and returns a genuinely new value. The behaviour is now documented and pinned by a test, so that fixing it forces the documentation to move with it.
Internal: domma-reactive moves to 0.4.2 - every published artefact now carries a version banner kept through minification, so a bundle is no longer anonymous and a stale dist/ cannot be published as a fresh one. 571 tests, up from 570.
domma.min.js has inlined the whole binding engine since v0.34.0, and almost none of it was reachable. Domma imported ten names from domma-reactive and re-exported nearly none, so the only route to a binding was a template inside Domma.component() - no way to bind server-rendered markup, and no way to register a custom binding. The evidence: across all 86 showcase pages there were zero live uses of data-bind-*, data-model, data-each or data-if outside a component, because outside a component there was no way to use them.
M.applyBindings(data, root, options) activates every binding attribute under a root on markup that already exists - server-rendered, hand-written, whatever - in place, with no build step and no second source of truth for the markup. Pass a Model and it is converted to model.tracked(), the read-tracked write-through view, so a data-model write lands in the model with validation, change notification and persistence intact rather than in a snapshot. Handlers arrive as options.methods, since a Model holds data and not behaviour.
M.registerBinding() and M.registerHelper() extend the vocabulary. The first is not a side door - all eight built-ins are registered through that same function, so anything a built-in can do a custom binding can do. The second exists because an expression deliberately cannot call a method on your data: data-bind-text="upper(name)" is the supported way to shape a value in markup. Deliberately not exposed: compile() and renderTemplate(), because Domma already has _.render and the two diverge - publishing both would be a trap.
Fixed - data-on-* and data-model in a component template. Methods live on the component context and were never merged into the template's data, so every event binding logged "did not resolve to a function" while every other binding on the same element worked. And data-model wrote back into a fresh plain snapshot, so the control looked right - what you see while typing is your own keystrokes - but the model never changed. Both were in the adapter between Domma and the engine; reads worked throughout, which is exactly why they survived.
Internal: the showcase conventions sweep, 186 vanilla-JS call sites across 43 pages down to zero, plus npm run validate:conventions to keep it there. Three of the five are not straight swaps, which is why it was done by hand. $(window) is deliberately not flagged - it is an empty collection, so a mechanical sweep would have silently disabled every window listener on the site. domma-reactive moves to 0.4.1. 570 tests, up from 542.
A browser-level test harness for the showcase, and the thirteen broken pages it found on its first run. There was no such coverage before: the unit suite exercises src/ modules in isolation and cannot see a page that renders wrong, so the failure mode was invisible - no error, no failing test, just a page that was broken for every visitor.
$('#el')[0] works. DommaCollection exposed .elements and .get(i) but no numeric index properties, so the most-typed thing in jQuery returned undefined - silently, until something dereferenced it. The DOM showcase documents $('.items')[0] as the same as get(0), and 65 call sites across the repository use the form. The collection is now array-like, so Array.from() works; it is still not iterable.
Domma.init() has never existed, and calling it threw. On the CSS-utilities showcase it sat at the top of the page's only script, so everything below it died - that page's reference table rendered nothing at all. Four separate documents told you to call it. Domma self-initialises when the bundle loads; the only startup call a page needs is Domma.icons.scan().
Also fixed: Pillbox was handed a <select> and a <div> on two showcases; the timeline showcase called its own deprecated alias six times on a page headed "Component Deprecated"; sidebar push mode targeted a .main-content that did not exist; and theme: 'light' is not a theme name. Added: four icons that pages were already using and that silently rendered nothing - alert-triangle, sidebar, window and gem.
Internal: npm run validate:showcase loads all 85 pages into jsdom against the built bundle and asserts each rendered and logged nothing. The baseline is empty - every page passes in strict mode, so any finding is a failure. Built as the prerequisite for the showcase conventions sweep: 191 violations across 54 pages, which is not something to rewrite without a harness.
One new Domma API: M.computed().value - the same read as get(). M.observable() has always been read through .value, while M.computed() offered only get(), so the two halves of the same idea disagreed about how you read them. It also made a computed unreadable from a template expression, where a method cannot be called: {{total.get()}} does not parse, and never will, because a call inside a render is a side effect.
M.observableArray().remove() now takes a value or a test. rows.remove(row) matches by identity as before; rows.remove(r => r.id > 2) removes everything the test accepts. A function used to be compared against each item by identity, never matched, and removed nothing without a word.
Fixed - && broke a binding inside a keyed block. A keyed block's body is captured by serialising DOM back to HTML, which escapes every & it writes, so data-bind-class="done && 'struck'" - the documented idiom - came back out as an entity and failed to parse. Attribute values read as expressions are now decoded in a single pass, so &lt; cannot double-decode.
Fixed - a row could not reach the list that owns it. Inside a list $data is the item, so $parent.remove($data) was the only way to name the parent, and it did not parse. data-on-* may now call a method on your data. The restriction is scoped rather than lifted: only the event binding may do it, and the evaluator still refuses to perform a method call, so {{ }}, data-if and data-bind-* remain reads with no side effects.
Internal: domma-reactive moves to 0.4.0 - keyed reconciliation, instance lifecycle and applyBindings(), plus the fixes above. 676 tests, every new guarantee mutation-tested.
No Domma API changes. Everything here arrives through the pinned domma-reactive 0.3.0, which Rollup still inlines - domma.min.js remains a single self-contained file.
A CSP-safe expression evaluator. Tokeniser, Pratt parser, AST walker and helper registry, supporting property paths, indexing, comparison, logical operators, ternaries, arithmetic and literals. No eval and no Function constructor anywhere - in the source or in any built bundle. Knockout evaluates bindings with the Function constructor, which fails under script-src 'self' without unsafe-eval; Domma's expressions work under a strict CSP. Property access through __proto__, constructor or prototype is refused in every form, including a computed key whose value is only '__proto__' at runtime.
A binding registry with registerBinding() and four behaviour bindings: data-on-* for events, data-bind-* for properties and attributes, data-model for two-way binding, and data-if, which removes the element rather than hiding it. The four existing mustache kinds now register through the same public API, so a custom binding is not a second-class citizen. There is deliberately no data-bind-html.
Fixed: seven rules painted with variables defined nowhere, so those backgrounds were simply absent. Added: two repository validators (npm run validate) that catch dead CSS classes and unreadable theme contrast - both failure modes that produce no error and no failing test, just markup that renders wrong.
A '300ms' wait ran for five minutes. parseWait() in the scribe effect tested endsWith('s') before endsWith('ms') - and because 'ms' also ends with 's', the millisecond branch was unreachable. Every millisecond-suffixed wait was multiplied by 1000, so a scribe sequence with a {wait: '300ms'} step appeared to stop after the first render.
Seconds ('2s') and raw numbers were unaffected, which is why it went unnoticed - the workaround was to pass milliseconds as a number. This was reported in the v0.25.2 notes as queued for a future patch and has been present ever since.
Internal: the helper was closure-local inside scribe() and therefore impossible to test. It is now a module-scope export, matching resolvePalette, pinned by tests that fail if the branch ordering is restored. The public Domma.effects surface is unchanged.
Fixed - every scaffolded project rendered unstyled. The kickstart templates were written in Bootstrap class names while Domma ships Tailwind-style utilities, so 143 class usages across 14 files resolved to nothing. Anyone who ran npx domma init got a project that looked broken on first load. Two components were dead rather than merely unstyled: the contact FAQ and docs pages used Bootstrap's collapse markup, and Bootstrap JS is never loaded - while contact.js and docs.js already called Domma.elements.accordion() and .tabs() against markup they could not recognise. Both work now.
The Reactivity showcase is a showcase, not a reference. It had six sections, every one named after a mechanism and opening by explaining it - no statement of the problem, no benefit, no guidance on when not to use it. Now twelve sections named for outcomes, demonstration before explanation, opening with the code you stop writing, and a When not to reach for it section that shows the traps running live. Prose grew from 506 to 4,118 words; all 26 interactive demos preserved. See the Reactivity showcase.
New: Try-it panes - editable code with live output on any showcase page - plus the .list-none and .no-underline utilities.
Internal: the template compiler moved into domma-reactive 0.2.0, completing the extraction milestone that had shipped half-done.
New: M.observable() and M.observableArray() - the reactive primitives beneath Models are now reachable from Domma. Use M.create() when you want a schema, validation and persistence; use an observable when you want one tracked value and nothing else. Both are the same functions published standalone as domma-reactive.
Fixed - components stopped updating. onUpdated() never fired for components whose templates contain no {{ }} bindings, so anything rendering its content imperatively rendered once and then went silent - the model updated and persisted, but the DOM was never told and nothing was thrown. This affected the todo, notes, contacts and markdown examples. Introduced in v0.30.0. Writes made from onUpdated must now converge on a value that compares equal, or the watcher re-triggers indefinitely.
Fixed - unreadable controls. Outline buttons used a transparent background with a color-mix() blend, so their contrast depended on whatever sat behind them and the label could land on its own background. Eighteen showcase rules set a background from a variable no theme overrides, with no text colour at all. .table-responsive was used in fourteen places and defined nowhere. The Reactivity showcase used .form-control and .col-md-*, neither of which exists in Domma.
Known issue: onUpdated still does not fire for fields absent from data() - declare every field you intend to change.
Site Update: A new full-bleed point-of-sale section on the Domma JS homepage, sitting between the feature grid and the Examples section, pitching Domma CMS without leaving the page. The section doubles as a working showcase of the effects library - six different Domma effects fire in choreographed sequence as it scrolls into view.
What's in the band:
- Looping headline - the
Domma.effects.scribetypewriter cycles four pitches: 'Want Domma JS power in a CMS?', 'Meet Domma CMS.', 'Markdown-first. Zero build step.', and 'Self-hosted. Open. Ridiculously fast.' - typing, holding, erasing and retyping in an infinite loop with a blinking cursor. - Stat tiles with stagger-in + counter cascade - four key numbers (27 shortcodes, 28 themes, 6 free plugins, 0 build steps) zoom-stagger into view, then
Domma.effects.counterticks each value up. The 'Build steps' tile counts down from 60 โ 0 for dramatic effect. - Animated CTAs - primary Get Started Free sits clean and bold; secondary See Features uses
Domma.effects.pulsefor a gentle attention pull. - Pastel ticker-tape burst -
Domma.effects.tickerTapefires a one-shot confetti burst (40 strips, pastel palette) scoped to the band when it reveals. - Float-blob ambient backdrop - both the band and the splash hero now use
.bg-ambient-float-blobsfor slowly-drifting indigo/blue radial gradients.
Effects choreographed: reveal โ scribe (looping) โ counter (cascade) โ tickerTape (burst) โ pulse - all triggered by a single IntersectionObserver with a requestAnimationFrame fallback for elements already in the viewport on initial load.
Accessibility note: Because this section is the pitch, its effects override prefers-reduced-motion via respectMotionPreference: false. The rest of the homepage still respects the user's motion toggle.
Known issue surfaced: The work uncovered an ordering bug in parseWait inside src/effects.js - endsWith('s') is checked before endsWith('ms'), so any millisecond-suffixed wait string is multiplied by 1000 (a '300ms' wait becomes 5 minutes). Worked around in the homepage by passing numeric milliseconds; fix queued for a future patch release.
Enhancement Release: The Carousel element gains a third transition mode and a fully configurable easing curve, with a small library of decorative demo gradients added to the showcase styling layer.
New transition mode - animation: 'crossfade': Outgoing and incoming slides transition opacity at the same time, briefly overlapping mid-transition. Cinematic and soft, ideal for hero imagery and feature reels where the existing 'fade' (clean swap) reads too abruptly. The outgoing slide remains position: relative for the duration of the overlap so the container holds its height - no mid-transition layout reflow. After the transition completes the incoming slide is promoted to the height-keeper role on a single setTimeout, with destroy-time cleanup.
New option - animationEasing: Any CSS timing function is now accepted ('ease', 'linear', 'ease-in-out', 'cubic-bezier(...)', etc.) and is threaded into both the fade/crossfade opacity transition and the slide-mode track transform transition. Default remains 'ease' - fully backwards-compatible.
Showcase rebuild: A dedicated Transitions section on the Carousel showcase page now demonstrates fade, crossfade, and a bouncy cubic-bezier easing example with autoplay + hover-pause, so the effects play themselves the moment the section comes into view.
Demo Gradient Palette: Nine reusable decorative gradients added to domma-showcase.css under the .demo-gradient-* namespace, organised by mood - aurora (vivid high-saturation), dusk (cinematic atmospheric), pop (bright energetic). Showcase-internal, not part of the framework's public CSS API.
Documentation: PHPStorm typings (CarouselOptions.animation widened to 'slide' | 'fade' | 'crossfade', animationEasing?: string added); src/CLAUDE.md module reference updated with transition mode descriptions; ELEMENTS.md gains a Transition Modes table and a Demo Gradient Palette reference.
Quick start:
Domma.elements.carousel('#hero', { animation: 'crossfade', animationDuration: 1400, animationEasing: 'ease-in-out' });
Feature Release: A new Chooser form input - Domma's 25th UI component - replacing the visual weight of native radio buttons and checkboxes when a choice deserves richer presentation. Cards or chips, single or multi-select, all four combinations driven by one unified API.
The four-way matrix:
- Card ยท single-select - Pricing-plan style; rich tiles with icon, label, description, and corner badge
- Card ยท multi-select - Feature picker with checkbox semantics inside the same card layout
- Chip ยท single-select - Compact pill row for terse choices
- Chip ยท multi-select - Tag picker with full keyboard support
Per-option metadata:
icon- any Domma icon name; renders inlinedescription- sub-text under the label (card + comfortable density only)tooltip- hover hint viadata-tooltipbadge: { text, type }- corner badge in any of five semantic colours, reusing the existing.badge-*classesrecommended: true- success-coloured border ring for the highlighted optiondisabled: true- muted, non-interactive, skipped by keyboard navigation
Visual polish - six top-level options:
accent- selected highlight colour; semantic name (primary,success,info,warning,danger) or any CSS colour stringaccentStyle- five distinct treatments:border(default),solid(filled tile),glow(transparent border + glowing ring),overlay(heavier translucent fill),underline(thick coloured bottom border)glow+glowColour- soft outer glow on the selected option, optional colour overrideshadow-none/sm/md/lg/xlapplied to every optionshadowColour- optional shadow tint
Theme awareness: Semantic colours map to existing Domma CSS variables and retint live across all 26 themes ร light/dark variants. Custom hex/rgb values become inline CSS custom properties on the chooser root, so they remain consistent across the four matrix combinations.
Form integration: Use type: 'chooser' in any F.create() blueprint and the form pipeline handles validation, model binding, and submission automatically. Required validation treats an empty array as empty for multi-select. Hidden native <input type="radio|checkbox"> elements are emitted when name is set so the value is captured by FormData even with JS disabled.
Accessibility: role="radiogroup" for single-select with arrow-key navigation; role="group" for multi-select with Tab + Space; aria-checked reflects state; disabled options skipped by keyboard; respects prefers-reduced-motion.
CMS Page Editor integration: The page-roller's form-section editor gains a draggable Add Chooser palette tile. Clicking it opens a slideover with a structured editor - field-level inputs, chooser-level toggles, full options editor with add/remove/reorder, six visual-option fields (accent, accent style, glow, glow colour, shadow, shadow colour), and a live preview pane that updates as you type. Save writes back into the formFields JSON textarea (still the canonical store). Click any rendered chooser tile in the canvas to re-open the slideover pre-populated for editing.
Showcase: Comprehensive page at /showcase/elements/chooser/ covering all four matrix combinations, density and columns demos, every per-option flag (with all five badge types), accent colour and accent style galleries, glow and shadow variants, in-form integration, standalone usage, and a long-form tutorial with three real-world patterns (pricing plan, tag picker, theme picker) plus a gotchas section.
Documentation: Full API reference in docs/API.md; blueprint shape and validation in docs/Blueprints.md; feature section with theme-awareness notes in docs/DommaDocumentation.md; PHPStorm typings include ChooserOptions, ChooserOption, ChooserBadge, ChooserInstance, and ChooserSemanticColour.
Quick start:
Domma.elements.chooser('#host', { variant: 'card', options: [โฆ] }); - standalone.
F.create({ plan: { type: 'chooser', variant: 'card', options: [โฆ] } }).renderTo('#form'); - inside a blueprint-driven form.
Feature Release: A new canvas-based ticker tape effect joins the Domma.effects family - colourful rectangular strips drop from the top of a container (or the entire viewport), sway, rotate, and gently fade before reaching the bottom. Reminiscent of a ticker-tape parade, perfect for celebration moments.
What it does:
- Two modes - Pass
nullfor a fixed full-page overlay, or a selector for a container-scoped effect; the canvas reflows automatically viaResizeObserver - Theme-aware palettes - The default
palette: 'theme'reads--dm-primary,--dm-success,--dm-warning,--dm-danger, and--dm-infofrom the active Domma theme, so the same call adapts to whichever theme is loaded - 10 named palettes -
rainbow,festive,gold,silver,pastel,mono,sunset,ocean,forest, andbridal; supply your own array of CSS colour strings for branded confetti - Continuous or one-shot - The default mode maintains a steady stream up to
densitystrips on screen;burst: truedrops a single batch (burstCountstrips) and lets the effect end naturally - ideal for form-success or achievement-unlocked moments - Tunable physics -
speed,sway,rotationSpeed, strip width / height ranges, andfadeStart(where in the fall fade begins) all adjustable - Standard control surface - Returns the same
{ pause, resume, stop, restart, destroy, isRunning, isPaused }object as every otherDomma.effectsmethod; honoursprefers-reduced-motionby default - Showcase - Dedicated page at
/showcase/effects/ticker-tape.htmlwith five demos: theme-aware container, palette picker (live swap), physics customisation sliders, one-shot burst, and full-page overlay - Documentation - Full API reference in
docs/API.md; PHPStorm typings updated; newparty-poppericon added to the seasonal set
Quick start:
Domma.effects.tickerTape(null); - full-page parade in the active theme's colours.
Domma.effects.tickerTape('#stage', { burst: true, palette: 'gold' }); - celebration burst on demand.
Feature Release: A full-featured Signature element - Domma's 24th UI component - providing a canvas-based handwriting pad for capturing signatures in forms, contracts, and consent flows.
Input & Drawing:
- Pointer Events API - Unified mouse, touch, and stylus input; pressure-sensitive line width on stylus devices with graceful fallback for mouse and touch
- Smooth Bรฉzier curves - Midpoint quadratic Bรฉzier algorithm prevents jagged strokes; variable lineWidth per segment from pressure data
- Normalised co-ordinates - Stroke points stored as 0-1 fractions so a
ResizeObservercan reflow all strokes correctly when the container is resized
Undo / Redo & Export:
- Full stroke history -
sig.undo()/sig.redo()operate stroke-by-stroke;sig.clear()is itself undoable;sig.clear(true)for a silent, irreversible wipe - PNG export -
sig.toBase64('png')returns a canvas data URL - SVG export -
sig.toBase64('svg')rebuilds paths from stored strokes using the same Bรฉzier algorithm as SVGM,Q,Lpath commands; output is visually identical to the canvas render
Toolbar & Type Fallback:
- Configurable toolbar - Colour swatches, pen-width dot buttons, undo, redo, and clear; driven by
coloursandwidthsoptions; hideable withtoolbar: false - Type fallback -
typeFallback: trueadds a Draw / Type toggle in the toolbar; Type mode renders cursive-font text to the canvas viafillText()sotoBase64()still returns an image;isEmpty()correctly reflects typed content
Form Integration & Config:
- Hidden input - Auto-populated with the base64 value after each stroke; the component works inside any
<form>for native submission - Blueprint support - Use
type: 'signature'in anF.create()blueprint; the form builder initialises the pad and syncs the model automatically - Config Engine -
signatureadded to the component map for declarative$.setup()initialisation
Accessibility: role="application" on the container, aria-pressed on all toolbar buttons, aria-live="polite" status bar, tabindex="0" on the canvas, and respectMotionPreference support.
Showcase: Dedicated page at /showcase/elements/signature/ with 13 sections including a live demo, pen options, export formats, undo/redo, type fallback, disabled state, form integration, config engine, contract-signing tutorial, and full API reference.
Feature Release: A full-featured List Group element - Domma's 23rd UI component - providing polished, consistent list styling for settings panels, navigation menus, notification feeds, and content lists.
CSS Classes:
- Base -
.list-groupcontainer with.list-group-itemitems; active, disabled, and.list-group-item-actionstates with hover/focus styling; sub-element helper classes for rich layouts:.list-group-item-icon,.list-group-item-content,.list-group-item-heading,.list-group-item-text,.list-group-item-trailing - Variants -
.list-group-flushstrips outer borders and radius for embedding inside cards;.list-group-smand.list-group-lgadjust padding and font size; 6 semantic colour variants (-primary,-secondary,-success,-danger,-warning,-info) apply a left-border accent with a tinted background - Numbered -
.list-group-numberedon an<ol>element prepends auto-incrementing counters via CSS - no JavaScript needed - Horizontal -
.list-group-horizontalarranges items side-by-side; responsive variants-sm(576 px),-md(768 px),-lg(992 px),-xl(1200 px) switch to horizontal only above the given breakpoint
JavaScript API: E.listGroup(selector, options) enables selectable lists with single or multi-select, roving tabindex keyboard navigation (Arrow keys, Home/End, Enter/Space), and full ARIA semantics (role="listbox", aria-selected, aria-disabled, aria-multiselectable). Public methods: select(i), deselect(i), toggle(i), selectAll(), deselectAll(), getSelected(), isSelected(i), enable(i), disable(i), getItems(), refresh(), destroy(). Callbacks: onChange, onSelect, onDeselect.
Config Engine: listGroup added to the supported component map for declarative $.setup() initialisation.
Showcase: Dedicated page at /showcase/elements/list-group/ with demos covering all CSS variants, numbered lists, horizontal layouts, interactive JS API (selection, multi-select, keyboard navigation), a settings-panel tutorial, API reference tables, and a CSS customisation guide. Linked from the Elements overview page and the Elements dropdown navigation.
Feature Release: A new Number Badge component extends Domma's badge system with positioned notification counters and presence indicators, available as CSS classes, a JavaScript API, and a <domma-number-badge> web component.
CSS Classes:
.badge-number- Standalone circular badge for single digits, expanding to a pill for multi-digit numbers viamin-width.badge-dot- Small 8px circular presence indicator (no text).badge-counter-wrapper/.badge-counter- Wrapper + absolute-positioned counter that overlaps the top-right corner of any element with a border ring- 8 colour variants -
-primary,-secondary,-success,-danger,-warning,-info,-light,-darkfor all three class families .badge-pulse- Reuses the existing pulse keyframe animation for urgent counters
JavaScript API: E.numberBadge(selector, options) wraps the target element in a <domma-number-badge> web component and returns a chainable instance with increment(), decrement(), setCount(), setVariant(), setDot(), setPulse(), and remove(). The badge auto-hides when count reaches zero in non-dot mode, and adds aria-live="polite" and role="status" for screen reader announcements.
Web Component <domma-number-badge>: Shadow DOM custom element with a <slot> for child content. Observed attributes: count, variant, dot, pulse, border-color. Full programmatic API mirrors the JS wrapper.
Config Engine: numberBadge added to the supported component map for declarative $.setup() configuration.
Showcase: Dedicated page at /showcase/elements/number-badge/ with CSS-only variant grids, interactive JS API demo (increment / decrement / dot / pulse / variant controls), web component examples, real-world patterns (mock navbar, inbox unread counts, online user presence list), API reference tables, and CSS customisation guide.
Enhancement Release: A complete opacity scale and a set of semantic translucency utilities have been added to domma.css, replacing the four sparse .opacity-* classes that previously existed.
Full Opacity Scale: 13 utility classes cover the complete range in steps of 10, with additional quarter-stop values - .opacity-0, .opacity-10, .opacity-20, .opacity-25, .opacity-30, .opacity-40, .opacity-50, .opacity-60, .opacity-70, .opacity-75, .opacity-80, .opacity-90, .opacity-100.
Semantic Translucent Utilities:
.translucent-light- Subtle transparency (opacity: 0.85); ideal for watermarks and overlays.translucent- Standard transparency (opacity: 0.70); for modal panels and frosted overlays.translucent-heavy- Strong transparency (opacity: 0.50); for ghost elements
All three classes include transition: opacity var(--dm-transition-normal) so state changes animate without extra CSS.
Frosted Glass: .translucent-glass applies a semi-transparent white background (rgba(255,255,255,0.75)) combined with backdrop-filter: blur(8px) and its -webkit- prefixed counterpart for broad browser coverage. Falls back gracefully to just the transparent background where backdrop-filter is unsupported.
Hover Variants: .translucent-hover, .translucent-light-hover, and .translucent-heavy-hover apply their respective opacity levels only on :hover, with the built-in transition. Useful for image grids and icon buttons.
CSS Custom Properties: All translucency values are driven by five new :root variables - --dm-translucent-light, --dm-translucent, --dm-translucent-heavy, --dm-translucent-glass-blur, and --dm-translucent-glass-opacity - making them straightforward to override per-theme.
Showcase: New dedicated page at /showcase/css-utilities/ with a visual opacity swatch grid, an interactive range-slider demo, translucent card examples, hover-variant demo boxes, a frosted-glass live demo over a coloured gradient, and a full reference table.
Enhancement Release: The left-border card accent pattern has been promoted from an inline style workaround to a set of first-class CSS classes, covering the five standard semantic colours.
New CSS Classes:
.card-accent- Primary colour left border (4 px), uses--dm-primary.card-accent-success- Success green left border, uses--dm-success.card-accent-danger- Danger red left border, uses--dm-danger.card-accent-warning- Warning amber left border, uses--dm-warning.card-accent-info- Info sky left border, uses--dm-info
Theme Variable Sweep: --dm-card-accent added to all 26 theme files as an alias for the primary colour, enabling per-theme accent customisation and supporting the JS color: 'accent' API option.
Showcase: Dedicated Card Accent section added to the Elements showcase; inline border-left style on the elements index replaced with the new class.
Repo Hygiene: Removed stale backup files (src/css/domma.css.original, public/showcase/css/domma.css.original), stale TODO (public/showcase/elements/CSS_CUSTOMISATION_SECTIONS_TODO.md), empty directories (public/showcase/js/, public/showcase/examples/), and added .superpowers/, firebase-debug.log, and release.json to .gitignore.
Enhancement Release: Full palette rewrite of both Christmas theme variants - moving from generic white/slate backgrounds to genuinely festive palettes.
Christmas Theme Overhaul:
- christmas-light ("Frosty Morning") - Ice-blue tinted backgrounds (
#F5F8FC) evoking frost on windows, deep pine-green text (#1A2A1E), classic Christmas Red primary (#C41E3A), rich Holly Green secondary (#2D6B3F), and frosty blue-tinted shadows - christmas-dark ("Christmas Eve") - Deep forest green backgrounds (
#0A1A10) replacing generic slate, warm candlelight cream text (#F0ECE4), bright red primary (#E85565) for dark-mode readability, bright holly secondary (#5CB875), and warm firelight-tinted shadows
Decorative CSS: Snow-dust radial-gradient dot pattern, red-gold-green gradient card top-borders via border-image, candy-stripe progress bars, and festive syntax highlighting.
Labels: Theme Roller and variant dot tooltips now read "Christmas Day โ๏ธ" / "Christmas Eve ๐".
Feature Release: A new Wedding theme palette - two variants (Day + Evening) built around antique gold (#C5A55A) and silver tones, bringing the total theme count to 26.
Wedding Theme:
- wedding-light (Day) - Warm cream backgrounds (
#FFFDF7), antique gold primary (#C5A55A), antique silver secondary (#A8A9AD), dark warm text (#2C2416), warm tan borders (#E8DCC8), and warm gold-tinted shadows - wedding-dark (Evening) - Deep midnight navy (
#12121F), gold primary with lighter hover (#D4B66A), warm cream text (#F0E8D8), indigo borders (#2A2A40), and subtle gold-glow shadows
Registered across all surfaces: Available in the Theme Roller dropdown (Day โ๏ธ / Evening ๐), the layout variant dot selector (count 24 โ 26), the themes showcase card grid, the Kickstart Builder theme picker, and the Domma.theme API (listThemes() / listBases()).
Feature Release: A new Mint theme palette - two variants (light + dark) built around cool spearmint and seafoam green tones, bringing the total theme count to 24.
Mint Theme:
- mint-light - Rich eucalyptus primary (
#2e9e6e), seafoam secondary (#5b8a7a), white surfaces with a subtle mint-tinted alt background (#f0faf5), near-black forest text (#1a2e25), and soft celadon borders (#c8e6d8) - mint-dark - Deep slate-900 backgrounds with a dark mint-tinted surface (
#1e3a2f), bright spearmint primary (#6dd5a8) for contrast, mint-tinted text (#d4f5e4), and dark teal borders (#2a4a3a)
Registered across all surfaces: Available in the Theme Roller dropdown, the layout variant dot selector (count 22 โ 24), the themes showcase card grid, and the Domma.theme API (listThemes() / listBases()).
Feature Release: The layout system now auto-generates frosted glass breadcrumb pills, overlaid top-left on hero sections and derived entirely from the page URL - no per-page configuration required.
Layout System Breadcrumbs:
- Frosted glass pill - Semi-transparent black backdrop with
backdrop-filter: blur(6px)sits at the top-left of every.herosection; falls back to a surface bar below the navbar on pages without a hero - URL-driven label generation - Path segments are mapped through a built-in dictionary (
dom โ DOM,http โ HTTP,spa โ SPA,theme-roller โ Theme Roller, etc.); anything unrecognised is title-cased automatically - Title extraction - The last crumb's label is pulled from
<title>, stripping common suffixes such asยท Domma Showcase, for a precise page name - Preset-driven - Enabled via
breadcrumbs.enabled: trueinpresets.json; active forshowcase:subpage,showcase:effects,showcase:element, andpublicpresets; off for landing, index, minimal, admin, and demo pages - Responsive - Pill shrinks at โค576px (font-size 0.7 rem, tighter padding)
- Two-layer DOM - Outer
<nav>holds the pill class; inner<div>is the Domma Breadcrumbs target, preserving component class isolation
Forms Fix: Checkbox fields no longer render a redundant label element alongside the input; the label is now handled by the checkbox markup itself.
Patch Release: Corrected label rendering logic in forms.js so that checkbox fields are excluded from the standard label output. Previously, a visible label element was injected alongside the checkbox even when the label was already embedded within the checkbox markup, causing doubled text on checkbox-type fields.
Feature Release: A new Grayve theme palette - two variants (light + dark) built around cool slate and steel-grey tones, bringing the total theme count to 22.
Grayve Theme:
- grayve-light - Steel-blue primary, light slate surfaces, dark navy text, and soft grey borders; a neutral professional palette suited to data-heavy interfaces
- grayve-dark - Deep charcoal backgrounds, lighter steel primary for contrast, muted slate borders, and off-white text
Registered across all surfaces: Available in the Theme Roller dropdown, the layout variant dot selector (count 20 โ 22), the Kickstart Builder theme picker, and the Domma.theme API.
Breadcrumbs CSS Refactor: Inline styles previously injected by elements.js at runtime for .dm-breadcrumbs-item, .dm-breadcrumbs-separator, and .dm-breadcrumbs-link were moved to elements.css as static rules. The separator selector was also corrected from .dm-breadcrumb-separator (singular) to .dm-breadcrumbs-separator (plural) for consistency.
Feature Release: Two new fully theme-aware colour palettes - Unicorn (pink and purple) and Dreamy (beige and biscuit) - each with light and dark variants, bringing the total theme count to 20.
Unicorn Theme:
- unicorn-light - Amethyst purple primary (
#9b59b6), hot pink secondary (#e91e90), white surfaces with soft lavender tint (#faf5fc), dark aubergine text (#2d1b3d), and light purple borders (#e1bee7) - unicorn-dark - Deep purple-black backgrounds (
#1a0e24/#241432), lighter purple primary (#ce93d8) for contrast, lighter pink secondary (#f48fb1), and muted purple borders (#4a2660)
Dreamy Theme:
- dreamy-light - Warm brown primary (
#8d6e63), warm cream surfaces (#fffdf9/#f5f0eb), dark chocolate text (#3e2723), and light biscuit borders (#d7ccc8) - dreamy-dark - Dark espresso backgrounds (
#1c1410/#2a1f1a), lighter taupe primary (#bcaaa4) for contrast, warm off-white text (#efebe9), and dark brown borders (#4e342e)
Registered across all surfaces: Both themes are available in the Theme Roller dropdown, the layout variant dot selector, the Kickstart Builder theme picker, and the Domma.theme API (listThemes() / listBases()). The variant dot count in the layout controls is updated from 16 to 20.
Codebase Cleanup: Removed six stale backup and .bak files (dom.js.backup-*, elements.js.backup-xss-*, three celebration theme .bak files), the one-off debug-wizard.html, firebase-debug.log, the auto-generated coverage/ directory, and the .playwright-mcp/ screenshot cache.
Maintenance Release: Resolved Google Search Console's 'duplicate without user-selected canonical' warnings, rebuilt the sitemap from scratch, fixed five dead links across the public site, and corrected theme-awareness in the Blueprint showcase.
Canonical Tags (119 pages): New scripts/add-canonical-tags.js inserts <link rel="canonical"> after </title> in every public HTML page. Root index.html maps to the bare domain URL; subdirectory index.html files use trailing-slash directory URLs; non-index .html files keep their extension. Template fragments (layouts/templates/*, examples/*/template.html) are skipped. The script is idempotent - safe to re-run.
Sitemap Rebuild: New scripts/rebuild-sitemap.js walks public/ and generates a fresh sitemap.xml from files that actually exist on disk. The previous sitemap contained 30+ phantom URLs; the new one has 101 real entries with priority tiers (1.0 root โ 0.8 hubs โ 0.7 module pages โ 0.6 sub-pages โ 0.3 legal). Also idempotent.
Dead Link Fixes (5):
- effects/javascript.html -
typewriter.htmlโscribe.html(effect was renamed in v0.14.x) - elements/timer - 'Progress Bar' /
../progress/โ 'Progression' /../progression/(component renamed in v0.14.0) - miniapps/address -
/docsโ/api/(documentation moved) - nav-showcase.json - Removed phantom 'Invoicing System' entry (miniapp not yet built)
- download page - Removed QuickStart Starter card, QuickStart Templates section, and dead
quickstart-latest.jsonfetch()call; all referenced archives are missing
Blueprint Showcase Theme Fix: The example areas (result panes, model state panels, blueprint method badges) were referencing non-existent CSS variables (--dm-bg-secondary, --dm-bg-tertiary) that fell back to hardcoded light-mode hex values, making them appear with light grey backgrounds in every dark theme. Fixed to use the correct Domma variables: --dm-surface-raised, --dm-surface, --dm-primary-bg, and --dm-primary. The code block also gained var(--dm-code-bg) so it adapts in dark themes.
Major Release: Introducing DommaComponents - a first-class Web Components system built into the Domma framework. Define reusable, self-contained UI components with Shadow DOM encapsulation, reactive state, lifecycle hooks, and scoped styles, all using a clean declarative API.
Domma.component() API:
- Custom element registration -
Domma.component('my-widget', { ... })registers a native Web Component - Shadow DOM encapsulation - Component markup and styles are fully isolated from the host page
- Reactive data -
data()function defines component state;onUpdated()fires on every change - Computed properties - Derived values that update automatically when data changes
- Methods - Event handler functions with access to
this.data,this.root, and all Domma APIs - Props with validation - Type-checked, default-valued attributes:
props: { initialMode: { type: M.types.string, default: 'basic' } } - Scoped styles - CSS defined in the
styleblock is injected into the Shadow DOM, invisible to the outside - External templates -
templateUrlworks inside components for substantial markup - Lifecycle hooks -
onBeforeMount,onMount,onUpdated,onUnmount
Four Example DommaComponents:
- <domma-calculator> - Scientific calculator with basic/scientific modes, memory functions, angle toggle, and full keyboard support
- <domma-todo> - Full CRUD task manager with priorities, status tracking, search, and localStorage persistence
- <domma-markdown-editor> - Real-time markdown editor with live preview pane, syntax helpers, auto-save, and keyboard shortcuts
- <domma-notes> - Rich note manager with categories, full-text search, overlay editor, and persistent storage
Each component is a single component.js file with a companion template.html, downloadable from the Examples pages and droppable into any project with a single tag.
Components Showcase: New dedicated showcase at /showcase/components/ with live demos, API reference, pattern documentation, and a full tutorial covering all component features.
Splash Page - Examples Row: Eight example tiles now appear on the landing page above MiniApps, each showing a description via Scribe typewriter animation on hover.
Major Release: Complete rebuild of the theming system achieving perfect theme-readiness across all 16 themes with full persistence support.
Theme Engine Persistence:
- Auto-save/load - Theme selections now persist across sessions and automatically load on page refresh
- Fixed initialization - Removed explicit theme parameters that were overriding stored preferences
- Seamless continuity - Users' theme choices respected throughout the application
Semantic Variable Migration:
- Replaced ~298 palette variables (
--dm-gray-*,--dm-slate-*) with semantic theme variables - Components now use
--dm-text,--dm-surface,--dm-border,--dm-hover-bg - Semantic variables automatically adapt to each theme's color scheme
Unified Dark Mode Detection:
- Introduced
data-modeattribute set by theme engine - Replaced 78+ inconsistent selectors with unified
[data-mode="dark"] - Consistent dark mode styling across entire component library
Theme File Corrections:
- Fixed ocean-light/ocean-dark and forest-light/forest-dark file content swaps
- Improved contrast in 5 dark themes (charcoal, silver, lemon, christmas, sunset)
- Converted opaque status colors to RGBA tints for better dark theme integration
Component Enhancements:
- Navbar & Footer: Dark hover backgrounds (
#1e293b) with white text in light mode - Rounded corners: Changed hover border-radius from
smtomdfor more polished appearance - Visual borders: Added 1px borders to navbar/footer for clear separation
- Card headers: Improved dark mode contrast with darker backgrounds
Bug Fixes:
- Fixed splash hero, login tabs, navbar branding, and cookie consent theming
- Corrected misuse of
--dm-text-inversevariable - All 298+ palette variable references replaced with theme-adaptive semantic variables
Impact: Zero text visibility issues. All components work perfectly across all 16 themes with proper contrast and readability.
Major enhancement to the SPA Router: Views can now load HTML templates from external files using templateUrl and partials, dramatically improving code organisation and maintainability.
Key Features:
- External Templates - Reference
.htmlfiles instead of embedding large HTML strings in JavaScript - Template Caching - Templates fetched once and cached per session for optimal performance
- Parallel Partials - Reusable template sections loaded concurrently
- Mustache Syntax - Full support for
{{variable}},{{#if}},{{#each}}, and{{> partial}} - Organised Structure - Recommended
js/views/templates/directory pattern
Impact: Views with 400+ lines of inline HTML reduced to ~50 lines of clean JavaScript. HTML files get proper syntax highlighting and editor support, while logic stays separate from presentation.
Enhanced Kickstart Template: New Domma projects now include built-in convention enforcement (CLAUDE.md, memory files) for AI-assisted development with Claude Code, Cursor, and similar tools.
Documentation: Comprehensive Template Files tutorial added to Router Showcase with 6 detailed sections covering all aspects of external template usage. Router now discoverable from the showcase hub.
Critical Fix: Resolved issue where reveal() and ripple() effects were disabled by CSS media queries even when JavaScript explicitly set respectMotionPreference: false.
Solution: Added data-force-animation attribute system to override CSS @media (prefers-reduced-motion: reduce) queries. Showcase demo pages now display animations correctly regardless of system motion preferences.
Code Cleanup: Updated effects showcase pages to use consistent respectMotionPreference: false configuration, removing obsolete variable references across 3 showcase files.
Introduced Domma.effects.typewriter() with character-by-character text rendering, blinking cursor, and powerful action queue system.
Features:
- Cursor styles: caret, block, underline
- Action queue:
render,wait,undoRenderactions - Per-character entrance effects: fade, bounce, glow
- Partial text deletion and configurable looping
- Full control: pause/resume/restart
Performance: GPU acceleration and hardware-accelerated animations for buttery-smooth 60fps rendering. Perfect for hero banners, animated intros, and dynamic text displays.
Transformed UK patron saint celebrations with horizontal-drifting flower petals:
- St. David's Day - Yellow daffodil petals
- St. George's Day - Red/white rose petals
- St. Andrew's Day - Purple heather petals
- St. Patrick's Day - Green clover petals
Bug Fixes:
- Fixed critical St. David's Day animation freeze caused by canvas state corruption (missing
ctx.restore()) - Replaced harsh X-shaped Saltire sparkles with elegant 4-pointed stars
- Reduced Guy Fawkes Night initial particle spam with gradual spawning (25% start, builds to 100% over 20 seconds)
All patron saint celebrations now feature realistic flutter animations and gentle vertical bobbing.
Major CSS Restructure: Proper build process with separation of concerns:
domma.css- Base styles + utilitiesgrid.css- Layout systemselements.css- 22 UI components
New Progression Component: Unified component with dual modes for event visualisation:
- Timeline mode - Chronological events
- Roadmap mode - Status-driven milestones
- 5 status states: planned, in-progress, completed, delayed, cancelled
- Interactive tooltips and responsive layouts
Enhanced celebrations showcase with accurate release timelines. Improved CSS build workflow with src/css source files and npm run build:css automation.
Bug Fixes: Resolved 'Invalid particle values' errors across all celebration themes (Halloween, Guy Fawkes, Christmas, St. Patrick's, St. George's, St. David's, St. Andrew's) by ensuring proper vx, vy, static, x, y initialisations.
Visual Enhancements:
- Fixed Christmas snow rendering and witch's broomstick orientation
- Implemented dynamic Batman logo display on the moon
- Added procedural fork-lighting for Halloween theme
- Visually overhauled Catherine Wheel for Guy Fawkes theme with fork-lighting for more realistic and dynamic effects
Redesigned SPA template views with professional layouts matching the QuickStart demo.
New Development Server: Added built-in npx domma serve command with:
- Live reload functionality
- Automatic MPA/SPA project detection
- Fixed template CSS paths
Critical Fix: Fixed broken CSS and JavaScript paths in SPA template that caused white screen errors after npx domma init.
Added npm scripts to generated projects for easier development workflow:
npm startnpm run serve
Server auto-prompts after npx domma init.
Introduced npx domma serve command with:
- Optional live-server integration
- Automatic MPA/SPA detection
- Custom port support (default: 3096)
Branding: Rebranded to DommaJS across splash page, navbar, and footer.
Enhancements: Improved feature card animations with smooth breathing effect.
Critical Fix: Fixed backToTop() bug that could destroy page body element when selector parameter was used.
SPA Router: Hash-based routing system with:
- Middleware support for route guards
- View lifecycle hooks (
onEnter,onMount,onLeave) - Navigation events
Ambient Effects: CSS-only ambient background effects (rotating glows, floating blobs, waves) for stunning visual presentations without JavaScript.
New production MiniApp for UK address lookup with postcode search.
Features:
- ONS local data integration
- Premium getAddress.io provider support
- Bar charts for analytics
- Star ratings for saved addresses
Build Domma Blueprints visually with drag-and-drop interface.
Features:
- 21 field types
- Live form preview with validation testing
- 12-column grid layout support
- Multi-format export: JavaScript, JSON, TypeScript
New CLI tools and setup documentation make getting started faster.
Run the post-install script to configure your environment automatically.
Major Release: Introducing Blueprints for unified data schemas.
Define your data structure once and use it with:
- Models (reactive data)
- Forms (auto-generation)
- CRUD operations (automatic API integration)
Updated distribution bundles with improved build process and version consistency across all bundle variants.
Framework now available via CDN for easier integration.
Simply include the script tag and start using Domma in your projects.
Core framework with:
- DOM manipulation (jQuery-compatible API)
- Utilities (Lodash-compatible)
- Reactive models
- UI components
- Theming system