GlassKit Elements
Complete reference for all 36 web components – built by Jungherz GmbH. Vanilla JS, Shadow DOM, native form participation.
Installation
CDN (quickest)
npm
Selective Import
Import only what you need for smaller bundles:
Theming
Set data-theme on the <html> element. All components sync automatically via a shared MutationObserver.
Token defaults and color-scheme. The elements put GlassKit’s token defaults on the document once, in @layer glasskit-defaults, so any stylesheet of yours wins over them. Since 1.20.0 that layer carries the --gl-* tokens only. color-scheme stays off the page: each element sets it on its own theme wrapper, so a select’s list, a date picker or a scrollbar inside an element matches its theme while the page keeps its own. Before, importing the elements switched the whole page to color-scheme: dark — text without a colour of its own turned white, links light blue, native controls dark.
The token names stay global, so a component of yours that reads var(--gl-…, fallback) gets GlassKit’s value. A page that declares the tokens itself, or wants them on part of the page only, switches the defaults off — the attribute is watched, so it may also come or go later:
The elements then take the tokens from wherever they inherit them: glasskit.css, or a block of your own on a container around them.
Density
For admin screens, desktop layouts and narrow forms, data-density="compact" on the <html> element makes every control denser: fields and buttons 40px high instead of 52 and 56, smaller toggles, checkboxes and radios, denser list rows and modal actions. The sizes are GlassKit's density tokens (GlassKit 1.21.0), so they reach every shadow root by inheritance — a project's own values too; with data-glk-defaults="off" the preset comes from glasskit.css on the page, like every other token. The same observer mirrors the attribute onto each element's theme wrapper, so switching at runtime needs nothing else.
Printing
Print dialogs leave out background graphics by default. With GlassKit 1.21.1 the parts that carry a state keep their colours on paper — checkbox, radio, toggle, progress, the current step, the chosen day, the slider thumb, the tone dots — inside the elements as well. A dark page prints its light text and surfaces on white paper; a page meant for printing switches to the light theme for it, and the elements follow:
Form Integration
All form components participate in native forms via ElementInternals: their value goes into FormData, and a reset restores it. Since 1.20.0 the form also learns whether they are valid. The inner field checks required, pattern, min, type="email" and the like, and the element reports its verdict: form.checkValidity() is false while a required field is empty, a submit stops at the first invalid field with the browser’s own message, reportValidity() focuses it, and validity, validationMessage and willValidate answer on the element. <glk-button type="submit"> submits its form and type="reset" resets it — before 1.20.0 both did nothing.
Labels are tied to their fields: a screen reader names the field by its label, and a click on the label text focuses it — as do element.focus() and a click on a <label for> that names the element. The hint of <glk-input> is read as the field’s description.
Disabled (since 1.22.3): every form element follows its own disabled and a <fieldset disabled> around it, as native controls do — dimmed, out of the tab order, no input, a submit button sends nothing, and FormData leaves it out. Before, a fieldset reached only the form data: the fields inside stayed usable, and a submit button sent the form empty. The disabled property reflects the attribute, as on a native control.
Content Components
<glk-title>
Styled heading with text shadow.
<glk-badge>
Inline status badge with color variants — and, with interactive, a filter chip.
interactive renders the badge as a real <button> — pointer cursor, hover tint, focus ring, keyboard operable — and makes it emit glk-click. selected marks the chip that is on and is mirrored to aria-pressed. Try the row: it moves selected on glk-click.
| Attribute | Type | Values |
|---|---|---|
variant | String | primary | success | warning (since 1.18.0) | error |
interactive | Boolean | Pressable chip; enables glk-click |
selected | Boolean | The chip that is on — deepens the badge's own color, sets aria-pressed |
Events: glk-click when interactive is set.
<glk-avatar>
Circular avatar with initials or image.
| Attribute | Type | Values |
|---|---|---|
size | String | sm | (default) | lg |
src | String | Image URL |
<glk-card>
Glass-effect content card.
| Attribute | Type | Description |
|---|---|---|
glow | Boolean | Adds gradient glow effect |
fill | Boolean | Stretches the card to the full height of its grid or flex cell and makes it a flex column, so margin-top: auto pushes a footer down (since 1.8.0). The card yields to its cell: content that cannot wrap, like a nowrap list subtitle, is truncated instead of widening the card (since 1.15.2) |
<glk-divider>
<glk-status>
Status notice with message.
<glk-empty>
Empty state for lists and result pages: icon plate, title, short text and one action. An empty title or text hides its element.
| Attribute | Type | Values |
|---|---|---|
title | String | Heading; hidden when empty |
text | String | Muted text; hidden when empty |
Slots: default (an <svg> icon; a plain circle as fallback), action. Parts: empty, icon, title, text, action.
Buttons
<glk-button>
Glass-styled button with three variants and size options.
| Attribute | Type | Values |
|---|---|---|
variant | String | primary | secondary | tertiary |
size | String | sm | md | lg | auto |
disabled | Boolean | Disables the button |
type | String | button | submit | reset — submit and reset act on the element’s form (since 1.20.0) |
Events: glk-click
type="submit" submits the form the element sits in, type="reset" resets it — once the click has finished bubbling, so a listener that calls preventDefault() on the click stops it, as on a native button. A form with invalid fields does not submit and shows the browser’s message. The submit event names the element as its submitter; .form is its form, and element.click() acts like a click. aria-expanded, aria-haspopup and aria-pressed set on the element are handed on to the native button inside, which is what screen readers read (since 1.20.0).
<glk-pill>
Small circular icon button (46x46px).
| Attribute | Type | Description |
|---|---|---|
label | String | Accessible aria-label |
disabled | Boolean | Disables the button |
aria-expanded, aria-haspopup and aria-pressed are handed on to the native button inside (since 1.20.0) — <glk-popover> sets aria-expanded on its trigger.
Form Components
All form components support name, value, disabled and participate in native <form> submission.
<glk-input>
Text input with label, hint, and error state.
| Attribute | Type | Description |
|---|---|---|
label | String | Input label |
type | String | text, email, password, ... |
placeholder | String | Placeholder text |
hint | String | Helper text, read as the field’s description |
error | Boolean | Error styling; marks the field invalid for screen readers (aria-invalid, since 1.20.0). It does not block the form — a server’s verdict arrives this way |
required | Boolean | Required field; while it is empty the form does not submit (since 1.20.0) |
readonly | Boolean | Read-only: focusable, selectable and submitted, but not editable; property readOnly (since 1.22.0) |
min, max, step | String | Passed to the inner field: range and step of the picker for dates, times and numbers (since 1.17.0) |
minlength, maxlength, pattern | String | Passed to the inner field (since 1.17.0) |
autocomplete, inputmode | String | Autofill and on-screen keyboard, passed to the inner field (since 1.17.0) |
Events: glk-input, glk-change
Slots (since 1.22.0): prefix and suffix put an icon, a currency, a unit or a button inside the field’s box, at its start and end — right to left they swap sides. Each shows only while something is slotted into it; its width is measured and the text keeps clear of it. A click on slotted text, an icon or a disabled control goes through to the field; enabled controls, <glk-button> and <glk-select> included, links and elements with tabindex take their own (since 1.22.2 — before, every slotted element took the click). Slot the control itself: one wrapped in a slotted element needs pointer-events: auto from the page. A container locked with pointer-events: none does not hold a control; lock it with inert, or disable the control. Their text is read with the field as its description. The box is the part box, the affixes prefix and suffix.
<glk-textarea>
Attributes: label, rows, placeholder, required, readonly (since 1.22.0), name, value, disabled. The label names the field.
<glk-select>
Children: native <option> elements. With required and an empty first option (value="") the form does not submit until a choice is made (since 1.20.0). The label names the field. A value — attribute or property — that names no option yet is kept and selected once its option arrives, until the user picks another (since 1.22.0; before, a value set as a property before the options were copied was lost). A form reset returns to the value attribute.
<glk-search>
The search field has no visible label; label names it for screen readers (since 1.20.0) — without it the placeholder is all they can say.
<glk-toggle>
Switch toggle with form participation.
| Attribute | Type | Description |
|---|---|---|
label | String | Toggle label |
checked | Boolean | Checked state |
disabled | Boolean | Disabled state |
required | Boolean | Must be on before the form submits (since 1.20.0) |
Events: glk-change → { checked: boolean }
The switch is the native checkbox inside, with role="switch": one control for screen readers, named by label, its state read from the checkbox. Since 1.20.0 the element itself carries no role, aria-checked or aria-disabled — style and select on [checked] and [disabled]. element.click() and a <label> around the element or naming it toggle it, as they do a native checkbox; the same holds for <glk-checkbox> and <glk-radio>. Without label the element is exactly as large as its track, 52 × 30 px.
<glk-checkbox>
required keeps the form from submitting until the box is ticked (since 1.20.0).
<glk-radio>
required on any radio of a group makes the group required; any checked radio satisfies it, as with native radios (since 1.20.0).
<glk-range>
| Attribute | Type | Description |
|---|---|---|
min | Number | Minimum (default: 0) |
max | Number | Maximum (default: 100) |
value | Number | Current value |
step | Number | Step increment |
<glk-segmented>
A small, exclusive choice as one control. Renders one <button> per option, the chosen one marked aria-pressed="true"; a value change only re-sets that attribute, so focus stays where it was. Form-associated: a surrounding <form> receives name=value, reset restores the initial value. Try it: Tab to a button, press Space. More options than fit: overflow="scroll" keeps one row that scrolls sideways and keeps the chosen option in view, overflow="wrap" breaks it into lines.
| Attribute | Type | Values |
|---|---|---|
options | JSON | [{ value, label, tone?, disabled? }] — tone: success | warning | error |
value | String | The chosen option's value |
full | Boolean | Buttons share the width |
overflow | String | scroll — one row that scrolls sideways, the chosen option kept in view; wrap — breaks into lines. Without it the row stays one line (since 1.17.0) |
label | String | aria-label of the group |
name | String | Form field name (form-associated) |
disabled | Boolean | The whole row (since 1.22.3); a <fieldset disabled> around it disables it too |
Events: glk-change { value } — only on a change made by the user. Part: group.
<glk-calendar>
One month with a day to pick. The days are buttons in a role="group", the chosen one aria-pressed="true", each named with its full date; month title, weekday names and the first day of the week come from Intl for the locale (week-start overrides). One tab stop: arrows move by a day or a week, Home/End to the ends of the week, PageUp/PageDown by a month — moving past the month’s edge shows that month and emits glk-month. Arrows only move focus; Enter or Space picks, so a calendar that opens a sheet on every pick stays quiet while the user looks around. Days outside min/max are aria-disabled: in the arrow path, announced, never picked. Form-associated like <glk-segmented>. Try it: Tab into the grid, use the arrows, press Enter.
| Attribute | Type | Values |
|---|---|---|
month | String | Shown month YYYY-MM; without it the month of the value, else of today. Reflected when the user navigates; a value set to another month flips to it |
value | String | Chosen day YYYY-MM-DD |
today | String | The day with the warm border; the browser’s day when missing |
min, max | String | Days outside cannot be picked (aria-disabled) |
marks | JSON | { "2026-09-30": ["error", "warning"] } — up to three tones per day: primary | success | warning | error |
locale | String | BCP 47 tag for names and week start; without it the page language — the lang of the nearest ancestor, usually <html lang>, also across shadow roots — else the browser language (page language since 1.19.2) |
week-start | Number | 0 = Sunday … 6 = Saturday; from the locale when missing, Monday where the browser cannot say |
label | String | aria-label of the day group |
prev-label, next-label | String | Names of the nav buttons; “Previous month” / “Next month” by default |
name | String | Form field name (form-associated) |
disabled | Boolean | Arrows and days (since 1.22.3); a <fieldset disabled> around it disables it too |
Events: glk-change { value } on a pick by the user; glk-month { month } when the user moves to another month. Properties: month, value, marks, locale, label, disabled. Parts: calendar, head, title, grid.
<glk-image-picker>
One image with a preview, resized on the client before it goes anywhere. Choose opens the file dialog from a real button; the file is decoded with createImageBitmap, the EXIF rotation of a phone photo applied, drawn onto a canvas no larger than max and handed out as a data URL in glk-change. The src property holds it and is not reflected, so a data URL of megabytes never lands in the DOM. A file the browser cannot decode — Chrome and HEIC, a corrupt file — emits glk-error instead of failing silently. Not form-associated: listen to glk-change and upload. Try it with a photo from your phone.
| Attribute | Type | Values |
|---|---|---|
src | String | Starting image: URL or data URL |
label | String | Visible label; names the group |
hint | String | Small muted line; none by default — what is accepted depends on accept and the browser |
round | Boolean | Circular preview, for avatars |
max | Number | Longest edge in px after resizing, default 1024 |
type, quality | String, Number | Output MIME (default image/jpeg; image/webp where the browser encodes it, PNG otherwise) and quality 0–1 (default 0.82) |
accept | String | File dialog filter, default image/* |
choose-label, change-label, remove-label | String | Button texts; “Choose”, “Change”, “Remove” by default |
Events: glk-change { dataUrl, width, height, size } after a pick, and on remove with an empty dataUrl and zeros; glk-error { message, name } when the file cannot be decoded. Property: src (not reflected). Parts: picker, preview, meta, label, hint, actions.
Feedback
<glk-progress>
| Attribute | Type | Values |
|---|---|---|
value | Number | 0 – 100 |
variant | String | success | error |
size | String | sm | (default) | lg |
<glk-modal>
Modal dialog with title, body, and action buttons. Since 1.20.0 it is a native <dialog>, opened with showModal(): it lies above everything else, the page behind it is inert for pointer, keyboard and screen readers, focus moves into it and goes back to where it came from when it closes, and its content is out of reach while it is closed. The title names the dialog.
Are you sure you want to proceed?
| Attribute | Type | Description |
|---|---|---|
open | Boolean | Show/hide |
title | String | Header title |
Methods: .show(), .close() Events: glk-close
Which element gets the focus on opening, browsers decide differently — Chrome takes the first field of the content, Safari the first action. Mark one with autofocus, in the content or among the actions, to settle it. Escape and a click on the dimmed area close the modal with its fade and emit glk-close; .close() does not emit. While it is open, everything outside is inert — a <glk-toast> shown then is neither clickable nor announced, so give feedback inside the modal or close it first.
<glk-toast>
Notification at the top of the screen that hides itself — and, since 1.19.0, one that offers something: with action-label (or show(message, { action })) it gets a button and an ×, and stays until one of them is used unless a duration is set. The action emits glk-action and closes the toast — unless a listener calls preventDefault() or shows the next message; the × and Escape emit glk-close. While the pointer or the focus is on it, it does not time out. It is a polite live region, so the message is announced.
| Attribute | Type | Values |
|---|---|---|
message | String | The text |
variant | String | success | error | warning — colours the icon and the action |
duration | Number | ms; 0 keeps it up. Default 3000 — with an action, no timeout unless set |
visible | Boolean | Shown |
action-label, action-value | String | The action button and the value glk-action reports (the label when missing) (since 1.19.0) |
dismissible | Boolean | An × without an action; an action always brings one (since 1.19.0) |
close-label | String | Name of the ×, “Close” by default (since 1.19.0) |
Methods: .show(message, variant, duration), .show(message, { variant, duration, action: { label, value }, dismissible }), .dismiss(). Slot: icon — replaces the built-in icon; leave its stroke unset and it takes the variant colour. Events: glk-action { action, label } (cancelable), glk-close (× or Escape), glk-dismiss (timed out).
<glk-popover>
Anchored dropdown / menu container. Wraps a trigger element (via slot="trigger") and its floating content. The element manages .is-open toggling, outside-click dismiss, and Escape-key close internally — no manual class toggling needed.
| Attribute | Type | Default | Description |
|---|---|---|---|
open | Boolean | false | Visibility state (reflected as .is-open on the inner surface) |
placement | String | bottom | top | bottom | start | end |
| Slot | Purpose |
|---|---|
trigger | Element that toggles the popover when clicked |
| (default) | Floating content shown while open |
Events: glk-open, glk-close
Methods: .show(), .close(), .toggle()
Heads-up: the method is deliberately called .toggle() — not .togglePopover(), which collides with the native HTMLElement.togglePopover() API.
The trigger tells whether the popover is open: it gets aria-expanded, also after it is swapped (since 1.20.0). <glk-button> and <glk-pill> hand it on to their native button.
<glk-sheet>
Bottom sheet — the mobile sibling of <glk-modal>. Opening and closing are animated by GlassKit's .glass-sheet block; the overlay is hidden only after transitionend, so no blurred layer idles behind the page, and under prefers-reduced-motion it switches at once. Scrim click and Escape close it and emit glk-close; show() / close() do not. inline embeds the panel without an overlay.
Sheet content: a list, text, a form. This one is inline.
Tap outside or press Escape to close.
| Attribute | Type | Values |
|---|---|---|
open | Boolean | Shown as an overlay |
inline | Boolean | In the flow, no overlay, always visible |
title | String | Heading and aria-label |
Methods: show(), close(). Slots: default (content), actions. Events: glk-close — only when the user closes it (scrim, Escape). Parts: overlay, sheet, title, body, actions.
Containers
<glk-accordion> + <glk-accordion-item>
Collapsible accordion sections.
| Attribute | Component | Description |
|---|---|---|
title | glk-accordion-item | Trigger text |
open | glk-accordion-item | Expanded state |
Events: glk-toggle → { open: boolean }
The trigger carries aria-expanded and aria-controls; a closed section is inert, so its links and fields are out of the tab order and out of the accessibility tree until it opens (since 1.20.0).
<glk-list> + <glk-list-item>
iOS-style grouped settings list. Items carry a leading icon, title + optional subtitle, and a trailing element. Dividers between items are drawn automatically — never add <hr> markup between items. Supports section headers, large icons, multi-line subtitles, trailing values, and semantic variants.
| Attribute | Component | Description |
|---|---|---|
header | glk-list | Section header text — uppercase label above the list |
flush | glk-list | Edge-to-edge variant — removes side margin and radius |
bare | glk-list | Strips background, border, shadow — for embedding inside <glk-popover> or <glk-card> |
title | glk-list-item | Primary text |
subtitle | glk-list-item | Secondary text (muted, optional) |
interactive | glk-list-item | Hover / focus / active states + emits glk-click |
center | glk-list-item | Centered single-text variant (e.g. for action rows) |
leading-lg | glk-list-item | Large 40×40 leading icon with rounded corners (for app icons) |
wrap | glk-list-item | Multi-line subtitle (up to 3 lines with ellipsis) |
detail | glk-list-item | Muted trailing value text (e.g. file size, version) |
variant | glk-list-item | danger (red destructive) or accent (primary color) |
| Slot | Component | Purpose |
|---|---|---|
leading | glk-list-item | Icon slot (24×24 SVG, or 32×32 with leading-lg) |
trailing | glk-list-item | Chevron, badge, or button (rendered after detail value) |
Events: glk-click on <glk-list-item> when interactive is set.
Framework-safe: both elements use pure Shadow DOM with slot projection — no child node cloning — so they compose cleanly inside lit-html templates, HybridsJS, React, Vue, and Svelte.
Build your own element
Since 1.14.0 the base classes are exported. A project element that extends GlkElement gets what every <glk-*> has: an open shadow root with GlassKit's stylesheet adopted (so .glass-btn & Co. work inside), the theme wrapper that follows data-theme and data-density, listeners re-armed when the element moves, and emit() for bubbling, composed events. GlkFormElement adds ElementInternals for native form participation. Try it — this button is a <demo-counter> defined on this page:
demo-count…
| Setup | Import |
|---|---|
| Bundle (ESM) | import { GlkElement, GlkFormElement } from '@jungherz-de/glasskit-elements'; |
| Per component | import { GlkElement } from '@jungherz-de/glasskit-elements/base.js'; — the very module the components import, so instanceof GlkElement holds across both. The per-component files leave @jungherz-de/glasskit/glasskit-styles.js external: a bundler resolves it, an import map needs an entry for it. Never next to the <script> bundle — that carries its own copy of the class. |
CDN / <script> | const { GlkElement } = GlassKitElements; |
Hooks: render() builds into this._wrapper; setupEvents() / teardownEvents() run on every connect / disconnect; onAttributeChanged(name, old, value) fires after the first render. Helpers: createElement(tag, classes, attrs), getBoolAttr() / setBoolAttr(), emit(name, detail). Return true from static get displayInline() for an inline element, and set static get observesLightDom() to have projectLightDom() re-run whenever light-DOM children change. Since 1.20.0, static get delegatesFocus() returning true hands the focus to the first field inside (on for GlkFormElement), and a GlkFormElement whose get _validityField() returns its native field reports that field’s validity on the host after every setFormValue() and attribute change. Since 1.22.3, applyDisabled(disabled) hands the disabled state to the native controls: override it in a GlkFormElement. It runs after the first render and whenever the element’s own disabled or a <fieldset disabled> around it changes — no fieldset reaches into the shadow root by itself.
Building a complete website?
GlassKit Elements is the app layer of the GlassKit family – web components for application UIs. For complete marketing websites – sections, i18n, SEO – the intended path is GlassKit Web, the official Astro template built on the same design language. The pure-CSS foundation underneath both is GlassKit.