Skip to content

@gluonjs/molecules ​

Reusable compositions built only from Core, Quarks, and Atoms.

ResponsiveActionBar composes one caller-owned primary action with optional summary, status, and compact control content. It exposes request-free ready/loading/disabled/error semantics, safe-area-aware sticky mobile and inline wide layouts, short-viewport static fallback, forced-colors and reduced-motion handling, and a 44px minimum action target. Safe-area behavior uses the platform environment variables; applications remain responsible for device-specific inset behavior.

@gluonjs/molecules at a glance ​

Runtime: browser · Release: 1.13.0

Documentation guide · npm · Source

Public API: @gluonjs/molecules

Install ​

sh
npm install @gluonjs/molecules

Quick start ​

ts
import { Accordion, ButtonGroup, Card, ChoiceGroup } from '@gluonjs/molecules';

Choose this package when ​

  • Reusable mid-level compositions built from Core, Quarks, and Atoms.
  • Form fields, control groups, notice surfaces, and dialog patterns.
  • Composition helpers that preserve native ownership.

Choose another boundary when:

  • Does not replace application-specific structure or business logic.
  • Some components rely on caller-provided heading and labeling hierarchy.
ts
import {
  Accordion,
  ButtonGroup,
  Card,
  ChoiceGroup,
  ControlField,
  DialogSurface,
  Disclosure,
  EmptyState,
  FormField,
  PasswordToggleField,
  InlineNotice,
  NavigationStrip,
  NavigationMenu,
  SearchField,
  SearchResults,
  ContextMenu,
  DropdownMenu,
  Menubar,
  OneTimePasswordField,
  SegmentedControl,
  TableRegion,
  Tabs,
  Toolbar,
  Tooltip,
  Stepper,
  FilterBar,
  DataList,
  ListboxField,
  ComboboxField,
  CommandPalette,
  TreeView,
  SortControl,
  createDialogSurfaceController,
} from "@gluonjs/molecules";

Card renders a native article. Its optional title is an h3; callers must place cards under a compatible heading hierarchy. FormField uses implicit native label association. An error sets the child input's aria-invalid state and exposes a visible role="alert"; helper text is visible supplementary copy. SortControl is a request-free labelled native select for product and data lists. Callers own option values, selected state, URL synchronization, and sorting effects; helper and error content receive deterministic relationships, while --gluon-sort-control-* variables provide tenant-safe styling hooks. createFormController() is the request-free behavioral companion for these field compositions. It exposes typed register(), values, touched/dirty state, field errors, async validate(), submit(), reset, subscriptions, and an abort-owned signal for validators and submit handlers. It does not render controls, read FormData, send requests, or depend on window/document. snapshot() and hydrate() carry serializable initial/value/error/touched state across SSR when the application supplies the same validator and submit handler on the browser side. The controller uses shallow record snapshots; nested schema traversal remains the responsibility of @gluonjs/json-forms or the application. ControlField generalizes that composition for any caller-rendered control. Its render callback receives stable control/label/helper/error IDs and matching ARIA relationships without cloning the control or owning its value, events, or validation. Pass the returned IDs/ARIA metadata to the official Atom or native control and forward required/invalid when that control supports them. ChoiceGroup renders a native fieldset and visible legend around caller-owned Checkbox or Radio options. It provides helper/error relationships, disabled fieldset propagation, and horizontal or vertical layout without taking option values, checked state, validation decisions, copy, or keyboard behavior away from the native controls. ButtonGroup renders an accessible named role="group" around caller-owned buttons. It preserves source and Tab order, supports horizontal or vertical layout, optional wrapping, and spaced or attached presentation, but never adds selection, menu, routing, tab, or pressed-state behavior to its children. SegmentedControl is a controlled, single-choice toolbar of native toggle buttons for a small finite option set. It exposes one Tab stop and Arrow/Home/End navigation, skips disabled options, and supports horizontal, vertical, and RTL layout. It is deliberately not a tablist or radio group: callers own the value, routing, panels, persistence, and async effects. Tabs implements the WAI-ARIA tablist/tab/tabpanel pattern with stable caller IDs, controlled selection, one Tab stop, disabled-option skipping, Arrow/Home/End navigation, and manual or automatic activation. Horizontal, vertical, RTL, overflow, forced-colors, and reduced-motion presentation are included; panel content, loading, routing, persistence, and effects remain caller-owned. DialogSurface composes the Quarks ARIA Dialog, Overlay, and createFocusScope contracts into a styled, controlled surface. Create one stable controller per openable surface, call controller.activate(trigger) when opening, pass it to the component, and call controller.deactivate() as part of closing or teardown. The controller defers initial focus until mount, contains Tab and Shift+Tab, and restores a connected trigger. Escape and direct overlay pointer dismissal call onDismiss; open state, close controls, copy, async state, and destructive decisions remain caller-owned. This component uses an ARIA dialog on a div; it deliberately does not call native HTMLDialogElement.showModal(), enter the top layer, or make background content inert. Applications that require the native-dialog boundary should own a native <dialog> lifecycle instead.

Tooltip provides a small, request-free description attached to a focusable trigger. It is deliberately non-interactive; use DialogSurface or a caller- owned popover for links, forms, or other interactive content. Stepper renders an ordered workflow with optional native links and caller-owned statuses. FilterBar provides the labelled form shell around caller-owned filter controls, counts, summaries, and reset actions. DataList renders native dl/dt/dd semantics for detail summaries and collapses to one column on small screens. ListboxField composes a visible label and helper/error relationships around the public Quarks listbox, preserving controlled values, disabled options, Arrow/Home/End navigation, and caller-owned change effects. ComboboxField adds a controlled native input with a stable listbox relationship, keyboard selection, disabled options, loading/empty feedback, active-option reporting, and caller-owned filtering and open state. CommandPalette renders a controlled labelled dialog with grouped commands, native combobox semantics, active-descendant keyboard navigation, loading and empty states, shortcuts, and caller-owned query filtering and execution. TreeView renders a labelled controlled ARIA tree from hierarchical nodes with stable IDs, expand/select callbacks, Arrow/Home/End navigation, disabled-node skipping, and reduced-motion/forced-colors styling. These components expose --gluon-* presentation hooks and keep data, routing, fetching, persistence, and mutations in the application. Disclosure renders native details and summary, preserving browser keyboard toggling, find-in-page expansion, semantics, and form behavior. Use open with onToggle for controlled state or defaultOpen for the initial native state. It deliberately has no silent disabled prop. When content is not yet available, pass unavailable: true with a concrete unavailableReason; the summary remains focusable, exposes aria-disabled, shows and references the reason, and prevents Enter, Space, and pointer activation. ResponsiveDisclosure is the responsive variant for panels such as mobile filters. It keeps one native details/summary tree, is always open outside compactBreakpoint, and starts with compactInitialOpen in compact view. A compact user's toggle is restored after breakpoint round-trips; a new compactResetToken deliberately replaces that remembered choice. It mirrors native open to summary[aria-expanded], works during SSR, removes its media query listener on disconnect, and uses the same constructable Disclosure stylesheet. Consequently its marker uses the existing reduced-motion rule, its border remains visible in forced-colors mode, and its logical grid follows RTL without a second responsive-specific style contract. Invalid IDs, breakpoints, initial state, and reset tokens throw stable GLUON_RESPONSIVE_DISCLOSURE_*_INVALID diagnostics. If matchMedia is missing or fails, the server-selected compact initial state is retained and the root exposes the corresponding stable diagnostic through data-gluon-responsive-disclosure-error. Accordion composes caller-owned Disclosure items inside a labelled group. It supports controlled single or multiple open values, stable item IDs, an explicit heading level, optional non-collapsible single selection, and Arrow/Home/End focus movement that skips unavailable summaries without changing native activation or Tab order. Callers retain item copy, open state, routing, loading, unavailable reasons, and effects; it deliberately does not implement a custom tree widget. InlineNotice renders bounded neutral, info, success, warning, or danger feedback with a non-color marker. Its auto announcement maps info/success to a polite status, warning/danger to an assertive alert, and neutral content to a static region; use polite, assertive, or off when message timing requires an explicit choice. Optional caller-owned action and dismiss controls render outside the live region. The application continues to own copy, lifecycle, events, retries, and dismissal state. EmptyState composes optional caller-owned media, a semantic heading level, body copy, and recovery action in compact or full layouts. It is intentionally static and adds no status, alert, or live-region semantics, avoiding repeated announcements on ordinary rerenders. When an empty result is newly produced by an asynchronous action, announce that transition separately with the bounded InlineNotice contract while keeping the persistent empty state static. TableRegion wraps a caller-owned native table in a named region. Optional summary copy labels the data set, while an optional scroll hint becomes visible and the horizontal viewport joins Tab order only when content actually overflows. Empty content is an explicit mutually exclusive variant. Captions, headers, rows, sorting, pagination, selection, editing, and virtualization stay caller-owned; the component deliberately does not implement DataGrid behavior. SearchField renders a native labelled type="search" input and submit button inside a role="search" form. Pass query and onQueryChange for controlled state, a stable caller-owned id, and onSubmit for the application's search action; it performs no request, ranking, routing, authentication, or analytics work. SearchResults renders caller-owned result children as grouped sections and lists, with optional counts/descriptions and explicit loading, empty, partial-failure, and disabled presentation states. PasswordToggleField renders exactly one native input and one native type="button" toggle. Pass a stable id, visible label, controlled value/visible, onInput, and onVisibleChange; callers must provide the localized showLabel and hideLabel. The toggle exposes aria-pressed and aria-controls, never announces or renders the password value, and does not submit its enclosing form. Native name, autocomplete, disabled, readOnly, required, invalid, helper, error, selection, focus, and DOM identity remain part of the input contract across rerenders. The component does not perform authentication, strength, breach, storage, transport, or policy work. Its IDs are derived from the required caller-owned field ID and must be unique per instance. DOM IDs must be non-empty and contain no whitespace; result group IDs must also be unique within one result composition. Group-heading relationships are namespaced by the root result ID, so separate compositions may reuse the same group IDs without colliding. Runtime values outside the documented state and heading-level unions fail closed. In a partial failure, available groups remain rendered beside the polite status message; its localized content remains its accessible announcement without a fixed English aria-label. Native input and form listener functions or { handleEvent } objects compose before the controlled callbacks, and preventDefault() suppresses the controlled callback where applicable. Submitter aria-label values remain caller-owned and the accessible name does not change during loading. OneTimePasswordField is a request-free controlled one-time-code composition. It renders bounded numeric or alphanumeric segments for keyboard, paste, and multi-character autofill editing, while only its optional hidden native value input has a name, so FormData receives exactly one value. The first editor owns autocomplete="one-time-code"; every editor receives the mode-specific inputmode. Required state applies to every visible segment so native invalid focus never targets the hidden submitted value. Pass a stable unique id; derived helper/error IDs are deterministic. The caller owns validation, submission, authentication, transport, and code verification. GLUON GOODS has no honest authentication or one-time-code journey, so it remains package-only and is recorded in examples/shop/FEATURES.md. NavigationStrip renders a named native nav, keeps destinations in source and Tab order, and shows 44px previous/next controls only when its viewport overflows. Resize and content changes update the available directions, while the exact [aria-current] destination is revealed without moving focus. A focused edge control remains focusable with aria-disabled="true" until focus moves, then returns to native disabled behavior.

ts
NavigationStrip({
  label: "Project sections",
  children: [
    q.a({ href: "#overview", children: "Overview" }),
    q.a({ href: "#activity", "aria-current": "page", children: "Activity" }),
    q.a({ href: "#settings", children: "Settings" }),
  ],
});

Breadcrumbs renders a caller-owned ordered path with native links for prior destinations and an aria-current="page" span for the current location. It does not inspect a router or derive URLs; mark an item as current when the current page is not the final item.

ts
Breadcrumbs({
  label: 'Catalog breadcrumb',
  items: [
    { label: 'Catalog', href: '/catalog' },
    { label: 'Lighting', href: '/catalog/lighting' },
    { label: 'Orbit lamp', current: true },
  ],
});

Pagination renders a named native nav with a bounded page window, labeled previous/next links, and an aria-current="page" destination. URL generation and state ownership stay with the caller through getPageHref, which keeps the molecule usable for shops, admin tables, and server-rendered routes.

ts
Pagination({
  currentPage: 4,
  totalPages: 9,
  siblingCount: 1,
  getPageHref: (page) => `/orders?page=${page}`,
});

NavigationMenu is the hierarchical counterpart for site and product navigation. It renders native nav, ul, li, a, and button semantics; it is not a command menu and does not know a router, permissions, analytics, or network state. Pass a document-unique root id, stable item IDs, and a controlled open array. Groups with an href render a native destination plus a separate disclosure button, while their nested items remain ordinary links. active, disabled, and unavailable are caller-owned decisions; unavailable items remain announced with a reason but do not navigate. Escape closes the deepest open group and returns focus to its trigger; outside pointer dismissal closes the controlled set. Arrow/Home/End traversal skips disabled or unavailable controls and follows the document direction. Derived panel and unavailable-message IDs are injectively escaped and namespaced by the root ID for deterministic SSR and hydration. Linked groups require caller-localized accessibleLabel copy for their separate disclosure button; unavailable items require an unavailableReason. Applications retain routing and analytics through native href and typed linkAttributes listeners.

ts
NavigationMenu({
  id: 'primary-navigation',
  label: 'Primary navigation',
  open: ['shop'],
  onOpenChange: (open) => console.log(open),
  items: [{
    id: 'shop', label: 'Shop', accessibleLabel: 'Open Shop navigation', href: '/shop', active: true,
    children: [{ id: 'new', label: 'New arrivals', href: '/shop?sort=new' }],
  }],
});

Styles use logical properties and shared Atom token names. ButtonGroup, Card, ChoiceGroup, ControlField, and FormField carry separate immutable stylesheet dependencies; NavigationStrip carries its own layout/control sheet, and FormField collects its nested Label and Input sheets through ordinary renderer traversal. NavigationMenu carries its own separately tree-shakable navigation stylesheet. Breadcrumbs and Pagination carry separate stylesheet dependencies with logical properties and CSS-variable overrides. Install the shared foundation and theme once through installUi(). The deprecated moleculeStyles aggregate remains the legacy Card/FormField sheet and cannot coexist silently with their exact rendering. moleculeManifest records every stable component, its accessibility contract, interactive example, browser test, and visual-regression evidence.

Applications can set --gluon-navigation-strip-gap, --gluon-navigation-strip-control-background, --gluon-navigation-strip-control-border-color, and --gluon-navigation-strip-control-color on the root through attributes.class or attributes.style without targeting implementation classes. NavigationMenu exposes --gluon-navigation-menu-gap, --gluon-navigation-menu-color, --gluon-navigation-menu-hover-background, --gluon-navigation-menu-surface, --gluon-navigation-menu-border, and --gluon-navigation-menu-shadow. ControlField exposes --gluon-control-field-required-color, --gluon-control-field-helper-color, and --gluon-control-field-error-color. ChoiceGroup exposes --gluon-choice-group-gap, --gluon-choice-group-helper-color, and --gluon-choice-group-error-color. ButtonGroup exposes --gluon-button-group-gap, --gluon-button-group-border-color, and --gluon-button-group-radius. SegmentedControl exposes --gluon-segmented-control-border-color, --gluon-segmented-control-background, --gluon-segmented-control-selected-background, --gluon-segmented-control-selected-color, and --gluon-segmented-control-radius. Tabs exposes --gluon-tabs-border-color, --gluon-tabs-background, --gluon-tabs-color, --gluon-tabs-selected-border-color, --gluon-tabs-selected-color, and --gluon-tabs-panel-padding. DialogSurface exposes --gluon-dialog-z-index, --gluon-dialog-overlay-background, --gluon-dialog-inline-size, --gluon-dialog-max-block-size, --gluon-dialog-background, --gluon-dialog-color, --gluon-dialog-border, --gluon-dialog-radius, --gluon-dialog-shadow, and section padding variables for header, description, content, and footer. Disclosure exposes --gluon-disclosure-border, summary gap/padding/color and weight variables, --gluon-disclosure-marker, marker color/size/motion, --gluon-disclosure-content-padding, and --gluon-disclosure-unavailable-color. Accordion exposes a minimal layout wrapper and inherits the Disclosure custom properties for every native item. InlineNotice exposes gap, padding, border, accent width, radius, background, color, and action-gap custom properties. Tone defaults remain contrast-aware, and application overrides must preserve readable text and state distinction. EmptyState exposes gap, minimum block size, padding, media size, heading/body width and typography, body color, and action-gap custom properties. TableRegion exposes gap, summary and hint colors, and --gluon-table-region-content-min-inline-size for application-owned column layouts that need horizontal overflow at constrained widths. SearchField exposes gap, label weight, control gap/size, submit size, color, and focus outline custom properties. SearchResults exposes group/list gaps, heading weight, colors, state sizing, padding, border, and radius properties. OneTimePasswordField exposes gap, control size, border, radius, color, focus-outline, helper/error color, background, and text color custom properties. PasswordToggleField exposes helper/error colors and focus outline custom properties through its separately tree-shakable passwordToggleFieldStyles. Breadcrumbs exposes --gluon-breadcrumbs-gap, --gluon-breadcrumbs-separator-color, --gluon-breadcrumbs-link-color, and --gluon-breadcrumbs-current-color. Pagination exposes --gluon-pagination-gap, --gluon-pagination-link-color, --gluon-pagination-current-background, --gluon-pagination-current-color, and --gluon-pagination-focus-color.

Card.attributes extends its native article. ControlField.attributes extends its outer div while structural content stays explicit. FormField.attributes extends the composed Input and FormField.fieldAttributes extends the outer native label. Both exclude owned children so callers cannot silently replace baseline composition. NavigationStrip.attributes extends its native navigation landmark while its internal viewport and controls stay owned. App-local Molecules use the public defineMolecule() metadata helper described in the extension contract.

GLUON GOODS creates one createFormController() for the checkout lifecycle, reuses it through hydration, and updates the same request-free state alongside the store-owned order data. It repeats FormField for its five required delivery inputs, uses ControlField for optional delivery instructions with deterministic help, and uses an app-local PurchaseAction defined with defineMolecule() in the same real checkout form. Native constraint validation remains authoritative for required controls and terms; the controller adds application-level validation and cancellation without taking over submission transport. Product configuration uses three ChoiceGroup fieldsets with native Radio options. Its catalog filter uses NavigationStrip to keep every category discoverable at constrained widths. Browser tests verify implicit labels, native constraint validation, overflow interaction, SSR/hydration styles, and teardown. The site header uses Toolbar for the search, bag, and mobile-menu action cluster. Its single Tab stop and arrow navigation do not change the individual native buttons or their existing customer actions. The catalog uses SegmentedControl for a URL-backed Grid/List view choice; the shop owns the route update and the corresponding product layout. Product details use Tabs for URL-backed Story and Details panels while the shop owns routing and restores focus to the activated tab after navigation. The bag uses DialogSurface for its labelled end drawer, overlay and Escape dismissal, initial close-button focus, focus containment, and trigger-focus restoration while the shop continues to own bag state and checkout decisions. The Shipping policy uses Accordion over native Disclosure items for URL-backed tracking, packaging, and remote-area details without replacing native summary behavior. The order-confirmation route uses a polite success InlineNotice for the real order ID, delivery email, total, and caller-owned continue-shopping action. The empty bag uses compact EmptyState composition with a semantic heading and caller-owned shop route while the enclosing DialogSurface retains focus and dismissal ownership. Toast is a request-free transient item. Pair it with createToastController() and ToastViewport() for bounded visible and waiting queues, stable safe DOM IDs, timeout, independent hover/focus pause ownership, a minimum five-second assistive-technology access window, and manual or programmatic dismissal. The viewport activates its controller only after its browser ref mounts. Calls to add() while inactive are validated but intentionally discarded; SSR therefore renders only the named empty viewport, arms no timer, and hydration cannot replay pre-mount feedback. A promoted item starts its complete deadline at promotion, while queued items do not age. Re-adding an ID replaces and promotes that record with a fresh deadline. items is a side-effect-free visible snapshot. The viewport keeps its internal lifecycle ref stable across controlled rerenders; replacing attributes.ref transfers the retained element to the new caller ref without deactivating the queue.

ts
const toasts = createToastController({ maxVisible: 2, maxQueue: 8 });

// Render once in the application root; mounting activates the controller.
ToastViewport({
  controller: toasts,
  label: 'Bag updates',
  dismissLabel: (record) => `Dismiss ${record.id}`,
});

toasts.add({
  id: 'bag-added',
  title: 'Added to bag',
  children: 'Orbit Lamp is ready in your bag.',
  tone: 'success',
});

maxVisible accepts 1–100, maxQueue 0–1000, and timeout values are finite safe integers up to 24 hours. minimumDuration cannot be below 5000ms. IDs must match [A-Za-z][A-Za-z0-9_-]*. Dispose the controller with its application owner; disposal clears all callbacks and is idempotent. Checkout uses TableRegion around its native captioned order table. The shop owns every header, row, price, and total; the molecule adds the region summary and narrow-layout overflow affordance without introducing grid interaction.

DropdownMenu, ContextMenu, and Menubar share the bounded MenuItem model. Every instance requires a caller-owned id; DropdownMenu and ContextMenu also require authoritative open and onOpenChange props. They provide true roving focus, buffered typeahead, disabled and separator semantics, explicit checkbox/radio groups, nested controlled submenu state, Escape/focus return, and RTL movement while preserving native links and buttons. Menubar renders an APG menubar without a synthetic dropdown trigger. Toolbar renders caller-described native buttons and links with horizontal/vertical roving focus. Styling is exposed through part, data-state, stable gluon-* classes, and component-scoped --gluon-menu-*/--gluon-toolbar-* properties. Routing, authorization, persistence, and command execution remain outside these compositions.

Version-matched documentation. Examples are checked against the public package contracts.