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.
What to read
Section titled “What to read”| If you… | Read |
|---|---|
| style the elements with your own CSS, or select them in tests | CSS and markup |
| listen to their events | Events |
| set their attributes in HTML or in templates | Attributes |
render gui-button | gui-button |
set errors on an element | Errors and touched |
| render a field without a label | Accessible names |
give gui-markdown or gui-markdown-text a parser, in TypeScript | The markdown parser |
import from @golemui/gui-components/internals | Packages |
Checklist
Section titled “Checklist”- 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
actionTypewithtypeongui-button, and dropinvalid. - Pass
touched, or seterrorsonly when they should show. - Give every field a
label. - In TypeScript, return
SanitizedHtmlfrom your markdown parser.
CSS and markup
Section titled “CSS and markup”Your CSS now wins over the elements’
Section titled “Your CSS now wins over the elements’”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:
.gui-form .gui-button button { border-radius: 0;}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.
Selectors
Section titled “Selectors”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:
.gui-validator ul li { font-weight: 600;}
@container gui-form (max-width: 480px) { /* … */}.gui-validator .gui-validator__error { font-weight: 600;}
@container gui-container (max-width: 480px) { /* … */}- Errors render as
div.gui-validator__errorinstead ofliin aul. - Narrow layouts query a container named
gui-container. A GolemUI form is also still namedgui-form, so your own@container gui-formqueries keep working there. - Right-to-left styles use
:dir(rtl), so adir="rtl"on any ancestor applies them. - A read-only
gui-selectis no longerdisabled. It stays focusable and hasaria-readonly.
Layout classes
Section titled “Layout classes”The .gui-flex classes are gone, and the grid classes have new names:
.gui-grid__widget--row { column-gap: 2rem;}.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.x | 2.0 |
|---|---|
.gui-flex, .gui-flex__widget and their modifiers | removed |
.gui-grid on the widget host | the 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.
.gui-tabs button[role='tab'].active { color: var(--brand);}gui-tab[aria-selected='true'] { color: var(--brand);}| 1.x | 2.0 |
|---|---|
nav.gui-widget | gui-tabs |
ul[role=tablist], li[role=presentation] | gui-tab-list |
button[role=tab], .active | gui-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-sentinel | removed |
The arrow keys, Home and End now select a tab, and fire the tabs’ change event.
Accordion
Section titled “Accordion”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.
.gui-accordion__section > button.active { font-weight: 700;}gui-accordion-item > details[open] > summary { font-weight: 700;}| 1.x | 2.0 |
|---|---|
div.gui-widget | gui-accordion |
.gui-accordion__section | gui-accordion-item > details |
.gui-accordion__section > button, .active | summary, 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.
.gui-alert-notification--warning { border-width: 2px;}gui-alert[variant='warning'] { border-width: 2px;}Events
Section titled “Events”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.
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" />;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.
<gui-select label="Country" (change)="onChange($event)" (blur)="onBlur()"></gui-select><gui-select label="Country" (gui-change)="onChange($event)" (gui-blur)="onBlur()"></gui-select><gui-select label="Country" @change="onChange" @blur="onBlur"></gui-select><gui-select label="Country" @gui-change="onChange" @gui-blur="onBlur"></gui-select>html`<gui-textinput label="Name" @input=${this.onInput} @blur=${this.onBlur} @inputError=${this.onError}></gui-textinput>`;html`<gui-textinput label="Name" @gui-input=${this.onInput} @gui-blur=${this.onBlur} @gui-input-error=${this.onError}></gui-textinput>`;select.addEventListener('change', (event) => save(event.detail.value));select.addEventListener('gui-change', (event) => save(event.detail.value));Every renamed event:
| 1.x | 2.0 |
|---|---|
input, change | gui-input while editing, gui-change on commit |
blur | gui-blur |
focus | gui-focus |
inputError | gui-input-error; an empty message clears the error |
partsChange, editStateChange | gui-parts-change, gui-edit-state-change |
dropdowntoggle, listtoggle | gui-dropdown-toggle, gui-list-toggle |
pillClick (range inputs) | gui-range-click |
pillclick, pillfocus, pillremove, pillexit, pillkeydown | gui-pill-click, gui-pill-focus, gui-pill-remove, gui-pill-exit, gui-pill-keydown |
pilledit, pilleditconfirm, pilleditcancel, pillsblur | gui-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, thengui-change. In 1.x, text fields fired onlyinputand the other fields onlychange. Text fields firegui-changeon 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.
Attributes are kebab-case
Section titled “Attributes are kebab-case”A multi-word attribute is now the kebab-case form of its property: autogrow is auto-grow,
countermode is counter-mode.
<gui-textarea label="Notes" autogrow countermode="current" /><gui-checkbox label="Subscribe" checkboxposition="right" />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.
<gui-textarea label="Notes" autogrow countermode="current"></gui-textarea><gui-checkbox label="Subscribe" checkboxposition="right"></gui-checkbox><gui-textarea label="Notes" auto-grow counter-mode="current"></gui-textarea><gui-checkbox label="Subscribe" checkbox-position="right"></gui-checkbox>Property bindings, such as [autoGrow]="true", don’t change.
<gui-textarea label="Notes" autogrow countermode="current"></gui-textarea><gui-checkbox label="Subscribe" checkboxposition="right"></gui-checkbox><gui-textarea label="Notes" auto-grow counter-mode="current"></gui-textarea><gui-checkbox label="Subscribe" checkbox-position="right"></gui-checkbox>Property bindings, such as :autoGrow="true", don’t change.
html` <gui-textarea label="Notes" autogrow countermode="current"></gui-textarea> <gui-checkbox label="Subscribe" checkboxposition="right"></gui-checkbox>`;html` <gui-textarea label="Notes" auto-grow counter-mode="current"></gui-textarea> <gui-checkbox label="Subscribe" checkbox-position="right"></gui-checkbox>`;Property bindings, such as .autoGrow=${true}, don’t change.
<gui-textarea label="Notes" autogrow countermode="current"></gui-textarea><gui-checkbox label="Subscribe" checkboxposition="right"></gui-checkbox><gui-textarea label="Notes" auto-grow counter-mode="current"></gui-textarea><gui-checkbox label="Subscribe" checkbox-position="right"></gui-checkbox>Properties set from JavaScript, such as textarea.autoGrow = true, don’t change.
readonly and maxlength keep their native spelling. A boolean attribute, such as value on a
checkbox, now reads "false" as false.
gui-button
Section titled “gui-button”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.
<gui-button actionType="submit" invalid={invalid} label="Save" />import { GuiButton } from '@golemui/gui-components/react';
<div className={invalid ? 'gui-button--invalid' : undefined}> <GuiButton type="submit" label="Save" /></div>;<gui-button [actionType]="'submit'" [invalid]="invalid" label="Save"></gui-button><div [class.gui-button--invalid]="invalid"> <gui-button type="submit" label="Save"></gui-button></div><gui-button :actionType.prop="'submit'" :invalid.prop="invalid" label="Save"></gui-button><div :class="{ 'gui-button--invalid': invalid }"> <gui-button type="submit" label="Save"></gui-button></div>html`<gui-button .actionType=${'submit'} ?invalid=${invalid} label="Save"></gui-button>`;html` <div class=${classMap({ 'gui-button--invalid': invalid })}> <gui-button type="submit" label="Save"></gui-button> </div>`;button.actionType = 'submit';button.invalid = invalid;button.type = 'submit';button.parentElement.classList.toggle('gui-button--invalid', invalid);New props: loading, size, href (renders a link), target, rel, name, value and
form. See Button.
Errors and touched
Section titled “Errors and touched”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:
<gui-textinput label="Email" errors={errors} />import { GuiTextinput } from '@golemui/gui-components/react';
<GuiTextinput label="Email" errors={errors} touched={touched} />;<gui-textinput label="Email" [errors]="errors"></gui-textinput><gui-textinput label="Email" [errors]="errors" [touched]="touched"></gui-textinput><gui-textinput label="Email" :errors.prop="errors"></gui-textinput><gui-textinput label="Email" :errors.prop="errors" :touched.prop="touched"></gui-textinput>html`<gui-textinput label="Email" .errors=${errors}></gui-textinput>`;html`<gui-textinput label="Email" .errors=${errors} .touched=${touched}></gui-textinput>`;field.errors = errors;field.errors = errors;field.touched = touched;Set touched to false to hide the errors.
Accessible names
Section titled “Accessible names”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:
<gui-tags />import { GuiTags } from '@golemui/gui-components/react';
<GuiTags label="Keywords" />;<gui-tags></gui-tags><gui-tags label="Keywords"></gui-tags><gui-tags></gui-tags><gui-tags label="Keywords"></gui-tags>html`<gui-tags></gui-tags>`;html`<gui-tags label="Keywords"></gui-tags>`;<gui-tags></gui-tags><gui-tags label="Keywords"></gui-tags>Accessibility has more.
The markdown parser returns SanitizedHtml
Section titled “The markdown parser returns SanitizedHtml”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.
import snarkdown from 'snarkdown';
const dependencies = { markdown: { parse: (md: string) => snarkdown(md) },};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.
Packages
Section titled “Packages”@golemui/gui-componentsno longer depends on the Forms packages. Its only peer dependency islit, withreactandvueas optional peers for their bindings.- The value types, such as
DateRange,TimeRange,Option,ListItem,FileItemandUploadService, are defined in@golemui/gui-components.@golemui/gui-sharedstill exports them, so imports from it keep working. @golemui/gui-components/internalsno longer exportscreateIntersectionObserverorNO_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.
New in 2.0
Section titled “New in 2.0”You don’t need these to upgrade, but they’re there:
- Plain HTML or any framework. Installation
covers React, Angular, Vue, Lit and plain HTML, with typed React components from
@golemui/gui-components/reactand Vue typings from@golemui/gui-components/vue. - Native forms. An element with a
nametakes part in a native<form>: the form submits its value,requiredand bounds block an invalid submit, andform.reset()restores it. - Server rendering in React, Next.js, Angular, Analog, Vue, Nuxt, Lit and Astro.
- Translated built-in strings with
configureMessages(). - New elements:
gui-tabs,gui-accordion,gui-alert,gui-dropdownandgui-multi-dropdown. - Stylesheets in parts,
tokens.cssandcomponents.css, next toindex.css, and acustom-elements.jsonmanifest for your editor. - A strict Content Security Policy, Trusted Types included.