Forms, switch product
announcements

GolemUI v1.4.0: dramatically faster core, multi-select widgets, and editable range pills

GolemUI v1.4.0 is out. v1.3.0 gave your own widget set its JSON schemas; v1.4.0 it’s about SPEED (more on that soon). It also adds two multi-select inputs and a way to edit a range without removing it and typing it again.

One of the biggest changes in this release is one you won’t find in a props table: GolemUI’s core is now unidirectional.

The form state is now the single source of truth for the widget tree. Every action derives the complete widget set from the current form data, and each renderer gets its widgets through a single view-model subscription. Widgets no longer need to announce themselves when they mount, there’s no second resolve pass on blur, and when a recompute produces the same content, the existing reference is preserved. In other words, the renderer only has work to do when something actually changed.

And on large forms, that makes a big difference.

8x sooner interactive React kitchen sink refresh, 3.0 s → 0.4 s; 2.5x on Angular
10x less main-thread blocking time in long tasks per refresh, 2.6 s → 0.25 s on React
2.7x faster keystroke handling React text input, 14.8 → 5.5 ms of sync work per key

Our 250-widget kitchen-sink form, with every widget type in the library across 36 tabs, now becomes interactive much sooner:

Renderer Last long task endsTime in long tasks
React 3.0 s to 0.4 s 7.9x 2.6 s to 0.25 s 10.7x
Vue 2.9 s to 0.5 s 6.3x 2.5 s to 0.2 s 12x
Lit 2.1 s to 0.5 s 4.2x 1.3 s to 0.23 s 5.9x
Angular 3.2 s to 1.3 s 2.5x 1.2 s to 0.57 s 2.2x
v1.3.0 → v1.4.0, medians of seven warm-cache refreshes of a production build in Chrome, same form definition for both.

That means the same form becomes interactive 2.5x to 8x sooner, depending on the renderer.

Typing is lighter too. In React, for example, a keystroke in a text input now takes 5.5 ms of synchronous work, down from 15 ms.

And this refactor is about more than performance. A widget set that is a pure function of the state is far easier to extend than one that is patched in place, and it unblocks a list of features we have wanted to build for a long time.

This release lays the groundwork. There’s more coming.

Until now every choice widget in gui stored one value. Picking several meant a tags field, which stores free text, or a repeater of checkboxes. v1.4.0 adds two widgets whose value is an array of the selected item values.

multiList is a scrolling, virtualized list where each row toggles on and off. Selection is shown on the row itself, with a check square and aria-selected, and the list is announced as multiselectable.

{
"kind": "input",
"type": "multiList",
"path": "toppings",
"label": "Toppings",
"props": { "items": ["Cheese", "Bacon", "Mushrooms"], "height": 200 }
}

multiDropdown is the compact form: a combobox with a filter box, the selected items rendered as removable pills to its left, and a panel that stays open while you toggle. The filter text survives a toggle, so picking several matches takes one search, and pills keep their labels even when the filter hides their items.

{
"kind": "input",
"type": "multiDropdown",
"path": "countries",
"label": "Countries",
"props": {
"placeholder": "Search countries",
"items": [
{ "label": "United States", "value": "us" },
{ "label": "Canada", "value": "ca" }
]
}
}

Both take items as strings, numbers, or objects with labelField and valueField, both accept an itemRenderer, and both validate with the array validator. There is deliberately no prop that caps the selection: a pick past maxItems is stored and flagged, so the user is told why instead of clicking a row that does nothing. The Multi List and Multi Dropdown pages have the props and live demos.

The eight range widgets (date, time and date-time inputs, pickers and calendars) show every committed range as a pill. Until now the only thing you could do with a pill was remove it. With allowEdit: true you can select it and edit it in place.

{
"kind": "input",
"type": "rangeDateInput",
"path": "stays",
"label": "Stays",
"defaultValue": [{ "start": "2026-09-07", "end": "2026-09-11" }],
"props": { "allowEdit": true }
}

Click a pill, or arrow onto it, and it shows a pencil. The pencil, or F2 or E on the focused pill, loads the range back into the widget: the segments in the inputs, the popover in the pickers, the day grid in the calendars. The pill’s label previews the change as you type or pick, Enter or the check icon confirms the replacement, and Escape or the cross icon cancels it. A confirmed range that overlaps a neighbour merges into it, and confirming an unchanged range is a cancel. In the calendars, selecting a pill dims the other ranges so you can see what you are reshaping.

Every tooltip and screen-reader announcement in the flow is a localizable prop, and on touch devices the pills and their icons grow through four new --gui-pill-* tokens under pointer: coarse. The Range Date Input page describes the flow in full; the other seven range pages describe what differs on their surface.

  • Dropdown has an icon and a toggle button. props.icon renders a leading icon the way Select and Textinput do, and a new arrow button opens the panel without leaving the input. Its label is props.toggleAriaLabel.

There are no code changes. Bump every @golemui/* package together, as always.

Previous GolemUI v1.3.0: JSON schemas for your own widget set Next GolemUI v1.5.0: server-side rendering and file uploads