GolemUI v2.1.0: overlays, sliders, one-time codes and row events
GolemUI v2.1.0 is out, and your pages just got a new layer. GolemUI Components now ships dialogs, drawers, popovers, menus, tooltips and toasts, built on the browser’s own top layer. There are new inputs too, one-time codes and sliders, and GolemUI Forms can use every one of them, down to a menu in each repeater row that knows exactly which row it belongs to.
Overlays built on the browser
Section titled “Overlays built on the browser”Browsers can now do most of what an overlay library used to do. The popover attribute and the <dialog> element show content in the top layer, above everything else on the page, with no z-index to manage. They close on Escape, keep the rest of the page inert behind a modal, and return the focus where it was.
The new elements build on that, instead of replacing it. Your panel stays your own markup, so it renders the same on the server and in the browser, and the element adds what the platform leaves out: placing the panel next to the button that opens it, flipping it when it doesn’t fit, drawers, and the ARIA patterns.
Dialogs and drawers
Section titled “Dialogs and drawers”gui-dialog opens your <dialog> as a modal, centered or as a drawer along any edge. It locks the page’s scroll, can close on a click on the backdrop, and fires gui-toggle when it opens and closes. A button opens it with no script: a native one or a gui-button, with commandfor and command. Open the filters:
Showing every card.
<gui-button label="Filters" variant="outlined" commandfor="filters" command="show-modal"></gui-button><gui-dialog placement="end" dismissible> <dialog id="filters" aria-labelledby="filters-title"> <form method="dialog" class="gui-grid"> <h2 id="filters-title">Filters</h2> <gui-checkbox label="Assigned to me"></gui-checkbox> <gui-checkbox label="Due this week"></gui-checkbox> <gui-checkbox label="Show completed"></gui-checkbox> <div class="gui-grid gui-grid--row gui-grid--justify-start"> <div class="gui-grid__cell"> <gui-button type="submit" label="Apply filters"></gui-button> </div> </div> </form> </dialog></gui-dialog><p id="filters-status" role="status">Showing every card.</p>
<script type="module"> const form = document.querySelector('#filters form'); const status = document.querySelector('#filters-status');
// The form closes the drawer. Show what the user picked. form.addEventListener('submit', () => { const boxes = [...form.querySelectorAll('gui-checkbox')]; const picked = boxes.filter((box) => box.value).map((box) => box.label); status.textContent = picked.length ? `Filtered by: ${picked.join(', ')}.` : 'Showing every card.'; });</script>alert makes it an alert dialog, for a confirmation the user has to answer. See Dialog.
Popovers and menus
Section titled “Popovers and menus”gui-popover places a panel with the popover attribute next to the button that opens it, in any of 12 positions, and keeps it there as the page scrolls. gui-menu turns a popover of buttons and links into a menu, with the keyboard of the menu button pattern: the arrow keys, typing to jump to an item, and a gui-action event with the item the user picked. Open the menu with the mouse or the keyboard:
Pick an action.
<button type="button" id="card-actions" class="gui-button gui-button--outlined" popovertarget="card-actions-menu"> Actions</button><gui-menu id="card-menu"> <div id="card-actions-menu" popover> <button type="button" value="edit">Edit</button> <button type="button" value="duplicate">Duplicate</button> <hr /> <button type="button" value="delete">Delete</button> </div></gui-menu><p id="card-status" role="status">Pick an action.</p>
<script type="module"> const menu = document.querySelector('#card-menu'); const status = document.querySelector('#card-status');
menu.addEventListener('gui-action', (event) => { status.textContent = `You picked ${event.detail.value}.`; });</script>Tooltips
Section titled “Tooltips”gui-tooltip describes the element its for names. It shows on hover and on keyboard focus, never on a tap, and screen readers read its text as the element’s description. It meets WCAG 1.4.13: Escape hides it, and the pointer can move onto it without it closing. Point at the button, or move to it with Tab:
<button type="button" id="save-draft" class="gui-button gui-button--outlined"> Save draft</button><gui-tooltip for="save-draft"> Saves your changes without publishing them.</gui-tooltip>See Tooltip.
Toasts
Section titled “Toasts”toast() shows a short message in the page’s gui-toaster, from any framework or none. Toasts queue when there are too many, pause while the user points at them, and screen readers announce them: an error at once, the rest when the user is idle. While a gui-dialog is open, they show inside it, so they stay usable.
import { toast } from '@golemui/gui-components/toast';
toast({ message: 'Draft saved.', variant: 'success' });toast({ message: 'Card archived.', action: { label: 'Undo', onAction: restore },});Try both:
See Toast.
Pickers and dropdowns open above the page
Section titled “Pickers and dropdowns open above the page”The panels of the date and time pickers, the dropdowns and the tags field now open in the top layer too. A parent with overflow: hidden, a modal dialog or a sidebar no longer cuts them off. They also follow the combobox pattern more closely: focus alone doesn’t open a panel, so moving through a form with Tab doesn’t pop anything up. A click in the field, the button at its end or Alt+↓ opens it. See Floating panels.
One-time codes and sliders
Section titled “One-time codes and sliders”gui-otp takes the digits of a verification code, one box per digit. Underneath it is one text input, so pasting a code, the browser’s autofill of codes from messages and screen readers all work as in any text field. A pasted 123-456 becomes 123456.
gui-slider picks a number on a track, and gui-range-slider a low and a high value, such as a price range. Both take steps, tick marks with labels, and an Intl.NumberFormat format for the value. Paste a code, or drag the thumbs:
<div class="gui-grid"> <gui-otp label="Verification code" groups="3,3"></gui-otp> <gui-slider label="Volume" step="10" marks></gui-slider> <gui-range-slider label="Price per night" maximum="500" step="10" format='{"style":"currency","currency":"EUR","maximumFractionDigits":0}' ></gui-range-slider></div>gui-radiogroup gets appearance="segmented", a row of joined buttons for a few short options, and gui-button gets button-aria-label, the name of a button that shows only an icon. See One-time code, Slider and Range slider.
What’s new in GolemUI Forms
Section titled “What’s new in GolemUI Forms”New widgets
Section titled “New widgets”GolemUI Forms get the same additions as widgets: One-time Code, Slider, Range Slider and Menu, in JSON and in the gui.* API, in all four frameworks.
import { toast } from '@golemui/gui-components/toast';import { gui } from '@golemui/gui-shared';
const formDef = [ gui.inputs.otp('code', { label: 'Verification code', validator: { required: true, exactLength: 6 }, }), gui.inputs.slider('guests', { label: 'Guests', minimum: 1, maximum: 8, }), gui.inputs.rangeSlider('price', { label: 'Price per night', maximum: 500, step: 10, }), gui.actions.menu({ label: 'Actions', items: [ { label: 'Edit', value: 'edit' }, { label: 'Delete', value: 'delete' }, ], onClick: (event) => toast({ message: `You picked ${event.detail.value}.` }), }),];The length of a code is its validator’s exactLength, a new rule of the string validator that also makes a partial code invalid.
Events that know their row
Section titled “Events that know their row”A menu fits naturally in a repeater row: edit this member, remove that one. Every row’s menu sends the same event, so until now the handler couldn’t tell which row it came from. In 2.1, event.row tells it: the row’s index, its position in every enclosing repeater, and its data path. One handler serves every row. Pick an action in the menu of a row:
See Events from a row.
Expressions stay read-only
Section titled “Expressions stay read-only”when rules, named states, {{ }} slots and i18n params run expressions over your form data. From 2.1 the form checks each one before it runs it for the first time. An expression that writes into the data or could run code, such as an assignment, push on an array or new, fails: the form reports it through formHealth with code 50, and the data stays as it was. This matters most for form definitions you don’t write yourself, from a CMS or an AI agent.
Expressions can still call your host functions and a list of methods that read a value, such as map, filter and includes. A method outside that list runs and prints a warning in development. Set expressionPolicy: 'strict' to make it fail too:
const config = { formDef, expressionPolicy: 'strict' as const };The MCP server runs the same check when it validates a form, so these errors show before the form ever runs. See What expressions can do.
React forms without a DOM
Section titled “React forms without a DOM”@golemui/react renders forms in React renderers that have no DOM, such as a terminal renderer or a native app. Create the form component of your widget set with headless: true: it renders the widget tree without the form element around it, and you pass the error UI built from your renderer’s own elements. react-dom is no longer a peer dependency of @golemui/react. See Rendering without a DOM.
Other changes
Section titled “Other changes”- Tab labels translate, as accordion labels do, and translation configs nested in a prop’s objects and arrays, such as menu item labels, translate too.
- The
formHealthcodes are listed in Configuration. - Data paths can’t contain
__proto__,constructororprototype: the form reports code 22. - Custom action widgets emit a detail with their click:
emitClick(detail)in React and Vue,click(detail)in Lit and Angular. - Lit forms report a widget that fails to load through
formHealth, as the other frameworks do, and a form moved in the DOM keeps rendering. - Fixes: the hidden rows of a repeater of plain values keep their index, and React tabs and alerts hydrate without mismatches.
Upgrading
Section titled “Upgrading”A few changes need your attention when you upgrade:
- Upgrade every
@golemui/*package to 2.1.0 at once. - Pickers and dropdowns no longer open on focus. If a test opens one by focusing it, click the field or press Alt+↓ instead.
- Their panels now open in the top layer, placed with inline styles. Remove any
z-indexoroverflowworkarounds you added for them, and restyle them through their classes, as Floating panels shows. - Check the expressions of your forms with the MCP server’s
validate-jsonorcheck-dx. An expression that writes into the data now fails with code 50. - In a React custom widget, send a click’s detail with
emitClick(detail).onClicktakes no detail. - A custom React
formHealthBoundarymust keepchildrenin the same position and under the same parent, or the form is built again when its health changes.
The full list of changes is in the changelog.