🇩🇪 DE

GlassKit Elements

Complete reference for all 36 web components – built by Jungherz GmbH. Vanilla JS, Shadow DOM, native form participation.

v1.22.3 Shadow DOM 36 Components Vanilla JS

Installation

CDN (quickest)

<!-- 1. GlassKit CSS --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.22/glasskit.min.css"> <!-- 2. GlassKit Elements --> <script src="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit-elements/dist/glasskit-elements.min.js"></script>

npm

npm install @jungherz-de/glasskit-elements @jungherz-de/glasskit
import '@jungherz-de/glasskit/glasskit.css'; import '@jungherz-de/glasskit-elements';

Selective Import

Import only what you need for smaller bundles:

import '@jungherz-de/glasskit-elements/components/glk-button.js'; import '@jungherz-de/glasskit-elements/components/glk-toggle.js';

Theming

Set data-theme on the <html> element. All components sync automatically via a shared MutationObserver.

<html data-theme="dark"> <!-- or "light" --> <script> // Toggle theme const theme = document.documentElement.getAttribute('data-theme'); document.documentElement.setAttribute('data-theme', theme === 'dark' ? 'light' : 'dark'); </script>

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:

<html data-glk-defaults="off">

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.

<html data-theme="dark" data-density="compact"> <script> // Back to the default density document.documentElement.removeAttribute('data-density'); </script>

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:

let printTheme = null; addEventListener('beforeprint', () => { printTheme = document.documentElement.getAttribute('data-theme'); document.documentElement.setAttribute('data-theme', 'light'); }); addEventListener('afterprint', () => { if (printTheme) document.documentElement.setAttribute('data-theme', printTheme); else document.documentElement.removeAttribute('data-theme'); });

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.

<form id="settings"> <glk-input name="username" label="Username"></glk-input> <glk-toggle name="notify" label="Notifications" checked></glk-toggle> <glk-button variant="primary" type="submit">Save</glk-button> </form> <script> document.getElementById('settings').addEventListener('submit', (e) => { e.preventDefault(); const data = new FormData(e.target); console.log(Object.fromEntries(data)); // { username: "...", notify: "on" } }); </script>

Content Components

<glk-title>

Styled heading with text shadow.

Page Heading
<glk-title>Page Heading</glk-title>

<glk-badge>

Inline status badge with color variants — and, with interactive, a filter chip.

Default Primary Success Warning Error
<glk-badge variant="success">Active</glk-badge>

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.

Active Applied Paused Done Pending
<glk-badge interactive selected>Active</glk-badge> <glk-badge interactive>Applied</glk-badge>
AttributeTypeValues
variantStringprimary | success | warning (since 1.18.0) | error
interactiveBooleanPressable chip; enables glk-click
selectedBooleanThe 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.

S MD LG
<glk-avatar size="lg">JD</glk-avatar> <glk-avatar src="photo.jpg"></glk-avatar>
AttributeTypeValues
sizeStringsm | (default) | lg
srcStringImage URL

<glk-card>

Glass-effect content card.

Card content here.
<glk-card glow>Highlighted content</glk-card>
AttributeTypeDescription
glowBooleanAdds gradient glow effect
fillBooleanStretches 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-divider></glk-divider>

<glk-status>

Status notice with message.

<glk-status message="No results found"></glk-status>

<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.

Book
<glk-empty title="No bookings yet" text="…"> <svg viewBox="0 0 24 24">…</svg> <glk-button slot="action" size="sm">Book</glk-button> </glk-empty>
AttributeTypeValues
titleStringHeading; hidden when empty
textStringMuted 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.

Primary Secondary Tertiary
<glk-button variant="primary" size="auto">Save</glk-button>
AttributeTypeValues
variantStringprimary | secondary | tertiary
sizeStringsm | md | lg | auto
disabledBooleanDisables the button
typeStringbutton | 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).

<glk-pill label="Menu"> <svg viewBox="0 0 24 24">...</svg> </glk-pill>
AttributeTypeDescription
labelStringAccessible aria-label
disabledBooleanDisables 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.

EUR
<glk-input label="Email" type="email" placeholder="you@example.com" hint="Required" error name="email"></glk-input>
AttributeTypeDescription
labelStringInput label
typeStringtext, email, password, ...
placeholderStringPlaceholder text
hintStringHelper text, read as the field’s description
errorBooleanError 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
requiredBooleanRequired field; while it is empty the form does not submit (since 1.20.0)
readonlyBooleanRead-only: focusable, selectable and submitted, but not editable; property readOnly (since 1.22.0)
min, max, stepStringPassed to the inner field: range and step of the picker for dates, times and numbers (since 1.17.0)
minlength, maxlength, patternStringPassed to the inner field (since 1.17.0)
autocomplete, inputmodeStringAutofill 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-input label="Price" inputmode="decimal"> <span slot="suffix">EUR</span> </glk-input>

<glk-textarea>

<glk-textarea label="Message" rows="4"></glk-textarea>

Attributes: label, rows, placeholder, required, readonly (since 1.22.0), name, value, disabled. The label names the field.

<glk-select>

<glk-select label="Plan" name="plan"> <option value="free">Free</option> <option value="pro">Pro</option> </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-toggle>

Switch toggle with form participation.

<glk-toggle label="Notifications" name="notify" checked></glk-toggle>
AttributeTypeDescription
labelStringToggle label
checkedBooleanChecked state
disabledBooleanDisabled state
requiredBooleanMust 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>

<glk-checkbox label="I agree" name="terms" checked></glk-checkbox>

required keeps the form from submitting until the box is ticked (since 1.20.0).

<glk-radio>

<glk-radio name="plan" label="Free" value="free" checked></glk-radio> <glk-radio name="plan" label="Pro" value="pro"></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>

<glk-range label="Volume" min="0" max="100" value="65"></glk-range>
AttributeTypeDescription
minNumberMinimum (default: 0)
maxNumberMaximum (default: 100)
valueNumberCurrent value
stepNumberStep 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.

waiting for glk-change…
<glk-segmented full label="Status" name="status" value="y" options='[{"value":"g","label":"Green","tone":"success"}, {"value":"y","label":"Yellow","tone":"warning"}, {"value":"x","label":"Locked","disabled":true}]'></glk-segmented>
AttributeTypeValues
optionsJSON[{ value, label, tone?, disabled? }] — tone: success | warning | error
valueStringThe chosen option's value
fullBooleanButtons share the width
overflowStringscroll — 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)
labelStringaria-label of the group
nameStringForm field name (form-associated)
disabledBooleanThe 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.

waiting for glk-change / glk-month…
<glk-calendar name="day" value="2026-09-22" min="2026-09-22" label="Pick a day" marks='{"2026-09-24":["error"],"2026-09-27":["success","warning"]}'></glk-calendar>
AttributeTypeValues
monthStringShown 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
valueStringChosen day YYYY-MM-DD
todayStringThe day with the warm border; the browser’s day when missing
min, maxStringDays outside cannot be picked (aria-disabled)
marksJSON{ "2026-09-30": ["error", "warning"] } — up to three tones per day: primary | success | warning | error
localeStringBCP 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-startNumber0 = Sunday … 6 = Saturday; from the locale when missing, Monday where the browser cannot say
labelStringaria-label of the day group
prev-label, next-labelStringNames of the nav buttons; “Previous month” / “Next month” by default
nameStringForm field name (form-associated)
disabledBooleanArrows 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.

waiting for glk-change…
<glk-image-picker round max="512" label="Profile photo" hint="JPG, PNG"></glk-image-picker> <script>picker.addEventListener('glk-change', e => upload(e.detail.dataUrl));</script>
AttributeTypeValues
srcStringStarting image: URL or data URL
labelStringVisible label; names the group
hintStringSmall muted line; none by default — what is accepted depends on accept and the browser
roundBooleanCircular preview, for avatars
maxNumberLongest edge in px after resizing, default 1024
type, qualityString, NumberOutput MIME (default image/jpeg; image/webp where the browser encodes it, PNG otherwise) and quality 0–1 (default 0.82)
acceptStringFile dialog filter, default image/*
choose-label, change-label, remove-labelStringButton 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.

Navigation

<glk-nav> + <glk-pill>

Horizontal navigation bar with circular icon buttons.

<glk-nav> <glk-pill label="Back"><svg>...</svg></glk-pill> <glk-pill label="Settings"><svg>...</svg></glk-pill> </glk-nav>

<glk-tab-bar> + <glk-tab-item>

Bottom tab bar navigation.

<glk-tab-bar> <glk-tab-item label="Home" active><svg>...</svg></glk-tab-item> <glk-tab-item label="Profile" badge="3"><svg>...</svg></glk-tab-item> </glk-tab-bar>
AttributeComponentDescription
labelglk-tab-itemTab label
activeglk-tab-itemActive state
badgeglk-tab-itemBadge count

Events: glk-tab-change → { tab: Element }

<glk-tab-dock> + <glk-tab-bar floating> + <glk-tab-accessory>

Pill-shaped, centered, floating tab bar inspired by iOS 26 Liquid Glass. Pair with an optional <glk-tab-accessory> capsule (e.g. search, compose). The active tab gets a soft radial Spotlight halo instead of the underline dot. Use .glass-bg--has-tab-bar-floating on the outer background.

<glk-tab-dock> <glk-tab-bar floating> <glk-tab-item label="Home" active><svg>...</svg></glk-tab-item> <!-- more items --> </glk-tab-bar> <glk-tab-accessory variant="accent" label="Compose"> <svg>...</svg> </glk-tab-accessory> </glk-tab-dock>
AttributeComponentDescription
floatingglk-tab-barPill-shaped Liquid Glass variant — Spotlight halo on the active item
accessory-leftglk-tab-dockPlace the accessory on the left of the bar (default is right)
labelglk-tab-accessoryaria-label for accessibility
variantglk-tab-accessoryaccent | success | error — filled colored capsule with white icon
disabledglk-tab-accessoryDisabled state

Events: <glk-tab-accessory> emits glk-click when clicked (suppressed when disabled).

Tip: Use .glass-bg--has-tab-bar-floating on the outer .glass-bg instead of --has-tab-bar to reserve the right amount of bottom padding.

<glk-steps>

Progress through a short flow, purely presentational: the current step carries aria-current="step", done steps show an SVG check. Narrowing in tight frames is the CSS block's container query (below 360 px only the current label stays) — the element knows nothing about it.

<glk-steps steps="Day,Slot,Dog,Confirm" current="2"></glk-steps>
AttributeTypeValues
stepsStringComma-separated labels
currentNumber0-based index of the current step
labelStringaria-label of the list (optional)

Property current reflects the attribute; steps accepts an array or comma-separated text. Part: steps.

<glk-date-strip>

A row of day chips that scrolls sideways — a booking horizon, the days around today. Chips are buttons in a role="group", the chosen one aria-pressed="true", each named with its full date. One tab stop: arrow keys move between chips, Home/End jump to the ends, Enter or Space picks. The chosen chip is scrolled to the middle of the strip after the first layout and on every value change — the strip alone scrolls, never the page. Weekday names come from Intl for the locale. Try it: the strip below starts with its 20th day chosen.

waiting for glk-change…
<glk-date-strip start="2026-09-22" days="29" value="2026-10-11" label="Pick a day" marks='{"2026-09-22":"success","2026-09-25":{"tone":"error","disabled":true}}'></glk-date-strip>
AttributeTypeValues
startStringFirst day YYYY-MM-DD; today when missing
daysNumberNumber of chips, default 7
valueStringChosen day YYYY-MM-DD
todayStringThe underlined day; the browser’s day when missing
marksJSON{ "2026-09-24": "warning", "2026-09-25": { "tone": "error", "disabled": true } } — tone: primary | success | warning | error; disabled makes the chip unpickable
localeStringBCP 47 tag for the weekday names and the chips’ aria-label; 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)
labelStringaria-label of the group

Events: glk-change { value } — only on a change made by the user. Properties: value, marks (object or JSON text), locale, label. Part: strip.

Feedback

<glk-progress>

<glk-progress label="Upload" value="75" variant="success"></glk-progress>
AttributeTypeValues
valueNumber0 – 100
variantStringsuccess | error
sizeStringsm | (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.

Open Modal

Are you sure you want to proceed?

<glk-modal title="Confirm" id="my-modal"> <p>Are you sure?</p> <div slot="actions"> <button class="glass-modal__action">Cancel</button> <button class="glass-modal__action glass-modal__action--primary">OK</button> </div> </glk-modal> // Methods document.getElementById('my-modal').show(); document.getElementById('my-modal').close();
AttributeTypeDescription
openBooleanShow/hide
titleStringHeader 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.

Success Error With action waiting for glk-action / glk-close…
<glk-toast id="toast"></glk-toast> // Show programmatically document.getElementById('toast').show('Saved!', 'success', 3000); // With an action: stays until Reload or × is used toast.show('A new version is ready', { action: { label: 'Reload', value: 'reload' } }); toast.addEventListener('glk-action', e => { if (e.detail.action === 'reload') location.reload(); });
AttributeTypeValues
messageStringThe text
variantStringsuccess | error | warning — colours the icon and the action
durationNumberms; 0 keeps it up. Default 3000 — with an action, no timeout unless set
visibleBooleanShown
action-label, action-valueStringThe action button and the value glk-action reports (the label when missing) (since 1.19.0)
dismissibleBooleanAn × without an action; an action always brings one (since 1.19.0)
close-labelStringName 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.

Open menu
<glk-popover placement="end"> <glk-button slot="trigger" variant="secondary">Open menu</glk-button> <glk-list bare> <glk-list-item title="Share" interactive></glk-list-item> <glk-list-item title="Delete" variant="danger" interactive></glk-list-item> </glk-list> </glk-popover> // Imperative API document.querySelector('glk-popover').show(); document.querySelector('glk-popover').close(); document.querySelector('glk-popover').toggle();
AttributeTypeDefaultDescription
openBooleanfalseVisibility state (reflected as .is-open on the inner surface)
placementStringbottomtop | bottom | start | end
SlotPurpose
triggerElement 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.

Open as overlay

Tap outside or press Escape to close.

Close
<glk-sheet id="sheet" title="Rebook to …"> <p>…</p> <glk-button slot="actions" onclick="sheet.close()">Close</glk-button> </glk-sheet> <script>sheet.show();</script>
AttributeTypeValues
openBooleanShown as an overlay
inlineBooleanIn the flow, no overlay, always visible
titleStringHeading 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.

Content for section 1. Content for section 2.
<glk-accordion> <glk-accordion-item title="FAQ" open> Answer here. </glk-accordion-item> </glk-accordion>
AttributeComponentDescription
titleglk-accordion-itemTrigger text
openglk-accordion-itemExpanded 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.

<glk-list header="Recommendations"> <glk-list-item title="Review media" subtitle="Save up to 1.38 GB." leading-lg wrap interactive> <svg slot="leading">...</svg> <svg slot="trailing">...</svg> </glk-list-item> <glk-list-item title="App" leading-lg detail="24 MB" interactive>...</glk-list-item> <glk-list-item title="Delete" variant="danger" center interactive></glk-list-item> <glk-list-item title="View all" variant="accent" center interactive></glk-list-item> </glk-list>
AttributeComponentDescription
headerglk-listSection header text — uppercase label above the list
flushglk-listEdge-to-edge variant — removes side margin and radius
bareglk-listStrips background, border, shadow — for embedding inside <glk-popover> or <glk-card>
titleglk-list-itemPrimary text
subtitleglk-list-itemSecondary text (muted, optional)
interactiveglk-list-itemHover / focus / active states + emits glk-click
centerglk-list-itemCentered single-text variant (e.g. for action rows)
leading-lgglk-list-itemLarge 40×40 leading icon with rounded corners (for app icons)
wrapglk-list-itemMulti-line subtitle (up to 3 lines with ellipsis)
detailglk-list-itemMuted trailing value text (e.g. file size, version)
variantglk-list-itemdanger (red destructive) or accent (primary color)
SlotComponentPurpose
leadingglk-list-itemIcon slot (24×24 SVG, or 32×32 with leading-lg)
trailingglk-list-itemChevron, 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:

waiting for demo-count…
import { GlkElement } from '@jungherz-de/glasskit-elements'; class DemoCounter extends GlkElement { static get observedAttributes() { return ['count']; } render() { // this._wrapper is the themed root; GlassKit classes work inside this._btn = this.createElement('button', ['glass-btn', 'glass-btn--secondary', 'glass-btn--sm', 'glass-btn--auto']); this._wrapper.appendChild(this._btn); } setupEvents() { this._onClick = () => { this.count += 1; this.emit('demo-count', { count: this.count }); }; this._btn.addEventListener('click', this._onClick); } teardownEvents() { this._btn?.removeEventListener('click', this._onClick); } onAttributeChanged() { /* re-render the label */ } get count() { return Number(this.getAttribute('count')) || 0; } set count(v) { this.setAttribute('count', v); } } customElements.define('demo-counter', DemoCounter);
SetupImport
Bundle (ESM)import { GlkElement, GlkFormElement } from '@jungherz-de/glasskit-elements';
Per componentimport { 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.