Skip to content
Components, switch product

From 1.x to 2.0

In 1.x, the gui-* elements came with GolemUI Forms. In 2.0 they are GolemUI Components, a library of their own that works in plain HTML or any framework, without the form engine. Getting there changed their stylesheets, the markup of tabs, accordions and alerts, and their events.

This guide is for code that uses the elements directly: in a page, in your own components, or in a custom widget of a GolemUI Forms app. To upgrade a Forms app, start with GolemUI Forms’ guide to 2.0, which links here for the elements.

If you…Read
style the elements with your own CSS, or select them in testsCSS and markup
listen to their eventsEvents
set their attributes in HTML or in templatesAttributes
render gui-buttongui-button
set errors on an elementErrors and touched
render a field without a labelAccessible names
give gui-markdown or gui-markdown-text a parser, in TypeScriptThe markdown parser
import from @golemui/gui-components/internalsPackages
  • Check your global CSS resets, which now override the elements’ styles.
  • Update the CSS and test selectors that target errors, grids, tabs, accordions or alerts.
  • Listen for the gui-* events.
  • Write multi-word attributes in kebab-case.
  • Replace actionType with type on gui-button, and drop invalid.
  • Pass touched, or set errors only when they should show.
  • Give every field a label.
  • In TypeScript, return SanitizedHtml from your markdown parser.

Every rule is in a cascade layer:

@layer golemui.tokens, golemui.base, golemui.components, golemui.forms, golemui.theme;

CSS outside a layer beats layered CSS whatever its specificity, so an override no longer needs a selector as specific as the elements’ own:

Before
.gui-form .gui-button button {
border-radius: 0;
}
After
gui-button button {
border-radius: 0;
}

Global resets win too. A plain button { … } or a { … } rule used to lose to the elements’ styles, and now overrides them. To keep the 1.x result, put your resets in a layer, and declare the order at the top of your CSS, before the GolemUI stylesheets load:

@layer reset, golemui;
@layer reset {
button {
all: unset;
}
}

Token overrides work as before: set the --gui-* variables on :root or any ancestor. Overriding styles has more.

The styles are no longer nested under .gui-form. Overrides that copied the old .gui-form … selectors keep working, because they are more specific than the new ones. Update these:

Before
.gui-validator ul li {
font-weight: 600;
}
@container gui-form (max-width: 480px) {
/* … */
}
After
.gui-validator .gui-validator__error {
font-weight: 600;
}
@container gui-container (max-width: 480px) {
/* … */
}
  • Errors render as div.gui-validator__error instead of li in a ul.
  • Narrow layouts query a container named gui-container. A GolemUI form is also still named gui-form, so your own @container gui-form queries keep working there.
  • Right-to-left styles use :dir(rtl), so a dir="rtl" on any ancestor applies them.
  • A read-only gui-select is no longer disabled. It stays focusable and has aria-readonly.

The .gui-flex classes are gone, and the grid classes have new names:

Before
.gui-grid__widget--row {
column-gap: 2rem;
}
After
.gui-grid--row {
--gui-layout-gap-md: 2rem;
}

Each gap step is a token, from --gui-layout-gap-none to --gui-layout-gap-xl. Set them on :root to change every grid.

1.x2.0
.gui-flex, .gui-flex__widget and their modifiersremoved
.gui-grid on the widget hostthe host has only .gui-field
.gui-grid__widget.gui-grid
.gui-grid__widget--row, --row--auto-fit, --column.gui-grid--row, .gui-grid--columns-N, .gui-grid--auto; a stack has no modifier
.gui-grid__widget--align-*, --justify-*.gui-grid--justify-*
inline column-gap, row-gap.gui-grid--gap-*
inline grid-column: span N on a cell.gui-grid__cell--span-N

Grid documents every class.

Tabs render gui-tabs and its parts. Tabs and panels keep their ids and data-cy attributes.

Before
.gui-tabs button[role='tab'].active {
color: var(--brand);
}
After
gui-tab[aria-selected='true'] {
color: var(--brand);
}
1.x2.0
nav.gui-widgetgui-tabs
ul[role=tablist], li[role=presentation]gui-tab-list
button[role=tab], .activegui-tab, [aria-selected='true']
section[role=tabpanel]gui-tab-panel
.gui-tabs--start-shadow, .gui-tabs--end-shadow[overflow-start], [overflow-end] on gui-tabs
li.gui-sentinelremoved

The arrow keys, Home and End now select a tab, and fire the tabs’ change event.

An accordion renders gui-accordion, and each section is a native <details>. Sections keep their ids and aria-labelledby. The header no longer has aria-expanded or aria-controls: <summary> already tells assistive technology whether the section is open.

Before
.gui-accordion__section > button.active {
font-weight: 700;
}
After
gui-accordion-item > details[open] > summary {
font-weight: 700;
}
1.x2.0
div.gui-widgetgui-accordion
.gui-accordion__sectiongui-accordion-item > details
.gui-accordion__section > button, .activesummary, details[open] > summary
section[role=region][hidden]section[role=region], hidden by its closed details

An alert renders gui-alert, and its id and its alert role are on the same element.

Before
.gui-alert-notification--warning {
border-width: 2px;
}
After
gui-alert[variant='warning'] {
border-width: 2px;
}

In 1.x, some events had native names, such as input and change, which collided with the browser’s own. Every event now starts with gui-, bubbles and is composed. The detail of each event is unchanged.

Before
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const select = ref.current!;
select.addEventListener('change', onChange);
return () => select.removeEventListener('change', onChange);
}, []);
return <gui-select ref={ref} label="Country" />;
After
import { GuiSelect } from '@golemui/gui-components/react';
return <GuiSelect label="Country" onGuiChange={onChange} onGuiBlur={onBlur} />;

The components in @golemui/gui-components/react have an onGui* prop for each event, typed with its detail.

Every renamed event:

1.x2.0
input, changegui-input while editing, gui-change on commit
blurgui-blur
focusgui-focus
inputErrorgui-input-error; an empty message clears the error
partsChange, editStateChangegui-parts-change, gui-edit-state-change
dropdowntoggle, listtogglegui-dropdown-toggle, gui-list-toggle
pillClick (range inputs)gui-range-click
pillclick, pillfocus, pillremove, pillexit, pillkeydowngui-pill-click, gui-pill-focus, gui-pill-remove, gui-pill-exit, gui-pill-keydown
pilledit, pilleditconfirm, pilleditcancel, pillsblurgui-pill-edit, gui-pill-edit-confirm, gui-pill-edit-cancel, gui-pills-blur
a change per toggled item (multi-list)gui-item-toggle
  • Every value element fires gui-input, then gui-change. In 1.x, text fields fired only input and the other fields only change. Text fields fire gui-change on blur or Enter.
  • Composite elements, such as the date picker, fire only their own events. The events of the calendar or the date field inside them no longer reach you.
  • A listener for an old name fails silently: it never fires, or it gets a native event with no detail.

A multi-word attribute is now the kebab-case form of its property: autogrow is auto-grow, countermode is counter-mode.

Before
<gui-textarea label="Notes" autogrow countermode="current" />
<gui-checkbox label="Subscribe" checkboxposition="right" />
After
import { GuiCheckbox, GuiTextarea } from '@golemui/gui-components/react';
<GuiTextarea label="Notes" autoGrow counterMode="current" />
<GuiCheckbox label="Subscribe" checkboxPosition="right" />

The React components take the properties, in camelCase, so their props have no attribute spelling to change.

readonly and maxlength keep their native spelling. A boolean attribute, such as value on a checkbox, now reads "false" as false.

actionType is now type, which also takes 'reset'. invalid is gone: a form shows the invalid state of its submit button with the gui-button--invalid class on an ancestor, which @golemui/gui-shared/forms.css styles.

Before
<gui-button actionType="submit" invalid={invalid} label="Save" />
After
import { GuiButton } from '@golemui/gui-components/react';
<div className={invalid ? 'gui-button--invalid' : undefined}>
<GuiButton type="submit" label="Save" />
</div>;

New props: loading, size, href (renders a link), target, rel, name, value and form. See Button.

In 1.x, an element showed its errors only when touched was true. In 2.0, touched is unset by default, and an element shows its errors as soon as you set them. To wait until the user has left the field, pass touched, or set errors only then:

Before
<gui-textinput label="Email" errors={errors} />
After
import { GuiTextinput } from '@golemui/gui-components/react';
<GuiTextinput label="Email" errors={errors} touched={touched} />;

Set touched to false to hide the errors.

A field takes its accessible name from its label. The generic names 1.x gave a field without a label, such as “Tags input”, are gone: such a field has no name for screen readers, and development builds log a warning. Give it a label:

Before
<gui-tags />
After
import { GuiTags } from '@golemui/gui-components/react';
<GuiTags label="Keywords" />;

Accessibility has more.

This applies only to TypeScript code that gives gui-markdown or gui-markdown-text a markdown parser in dependencies.

Both elements insert the parser output as HTML without sanitizing it. To make that visible, parse now returns SanitizedHtml instead of string. A parser that returns a plain string no longer compiles.

Before
import snarkdown from 'snarkdown';
const dependencies = {
markdown: { parse: (md: string) => snarkdown(md) },
};
After
import type { SanitizedHtml } from '@golemui/gui-components';
import snarkdown from 'snarkdown';
const dependencies = {
// `sanitizeHtml` is your HTML sanitizer.
markdown: {
parse: (md: string) => sanitizeHtml(snarkdown(md)) as SanitizedHtml,
},
};

The parser is the same object in every framework, and plain JavaScript needs no change.

When the Markdown can come from users or stored data, sanitize the parser output. Snarkdown and Marked keep raw HTML and javascript: links. Micromark and markdown-it escape both with their default options. When all the Markdown comes from your own source code, the cast alone is enough. Untrusted Markdown has an example that sanitizes with DOMPurify.

  • @golemui/gui-components no longer depends on the Forms packages. Its only peer dependency is lit, with react and vue as optional peers for their bindings.
  • The value types, such as DateRange, TimeRange, Option, ListItem, FileItem and UploadService, are defined in @golemui/gui-components. @golemui/gui-shared still exports them, so imports from it keep working.
  • @golemui/gui-components/internals no longer exports createIntersectionObserver or NO_AVAILABLE_TIMES_MESSAGE.
  • If you also use GolemUI Forms, upgrade every @golemui/* package to 2.0 at once: each one depends on the exact version of the others.

You don’t need these to upgrade, but they’re there: