Skip to content

Utilities ​

Three modules in theme/frontend/lib/ provide shared functionality to all islands.

events.js ​

Path: theme/frontend/lib/events.js

The event system for inter-component communication. See Event System for architecture and the event catalog.

Events ​

String constants for event type names. Use these instead of raw strings to catch typos at import time.

js
import { Events } from '@/lib/events'
ConstantValueScope
Events.CART_ADDED'cart:added'Global (document)
Events.CART_UPDATED'cart:updated'Global (document)
Events.CART_ERROR'cart:error'Global (document)
Events.VARIANT_CHANGED'variant:changed'Local (bubbles)

createEvent(type, detail) ​

Creates a CustomEvent with bubbles: true.

ParameterTypeDefaultDescription
typestringrequiredEvent type, typically from Events
detailobject{}Payload on event.detail

Returns: CustomEvent

js
document.dispatchEvent(
  createEvent(Events.CART_ADDED, {
    source: 'product-form',
    productId: data.id,
    sections: data.sections,
    activeElement,
  })
)

listen(target, type, handler) ​

Subscribes to an event with automatic AbortController wiring. Returns the controller for cleanup in disconnectedCallback.

ParameterTypeDescription
targetEventTargetElement or document to listen on
typestringEvent type
handlerFunctionEvent handler

Returns: AbortController

js
connectedCallback() {
  this.eventControllers = [
    listen(document, Events.CART_ADDED, this.onCartAdded.bind(this)),
    listen(document, Events.CART_UPDATED, this.onCartUpdated.bind(this)),
  ]
}

disconnectedCallback() {
  for (const c of this.eventControllers ?? []) {
    c.abort()
  }
}

a11y.js ​

Path: theme/frontend/lib/a11y.js

Accessibility utilities for focus management, keyboard navigation, and ARIA attributes.

getFocusableElements(container) ​

Returns all visible, focusable elements within a container.

ParameterTypeDescription
containerElementDOM element to search within

Returns: Element[]

Matched selectors: summary, a[href], button:enabled, [tabindex]:not([tabindex^='-']), [draggable], area, input:not([type=hidden]):enabled, select:enabled, textarea:enabled, object, iframe.

js
const focusable = getFocusableElements(this.querySelector('.modal-content'))
if (focusable.length > 0) focusable[0].focus()

trapFocus(container, elementToFocus) ​

Traps keyboard focus within a container. Tab wraps from last to first; Shift+Tab wraps from first to last. Only one trap can be active — calling trapFocus again removes the previous one.

ParameterTypeDefaultDescription
containerElementrequiredElement to trap focus within
elementToFocusElementcontainerElement to focus immediately
js
open() {
  this.detailsContainer.setAttribute('open', true)
  document.body.classList.add('overflow-hidden')
  trapFocus(
    this.detailsContainer.querySelector('[tabindex="-1"]'),
    this.detailsContainer.querySelector('input:not([type="hidden"])')
  )
}

removeTrapFocus(elementToFocus) ​

Removes the active focus trap and optionally returns focus to a specific element.

ParameterTypeDefaultDescription
elementToFocusElement | nullnullElement to focus after removing trap. null = don't move focus.
js
close(focusToggle = true) {
  removeTrapFocus(focusToggle ? this.summaryToggle : null)
  this.detailsContainer.removeAttribute('open')
  document.body.classList.remove('overflow-hidden')
}

onKeyUpEscape(event) ​

Handles Escape on <details> elements. Closes the nearest open <details> ancestor, updates aria-expanded, and returns focus to <summary>.

ParameterTypeDescription
eventKeyboardEventThe keyup event
js
this.detailsContainer.addEventListener('keyup', onKeyUpEscape, { signal })

initDisclosureWidgets(summaries) ​

Initializes ARIA attributes and keyboard behavior for <summary> elements. Sets role="button", aria-expanded, aria-controls, and wires up click/keyup handlers.

ParameterTypeDescription
summariesNodeList | Element[]<summary> elements to initialize
js
// From theme.js entry point
const summaries = document.querySelectorAll('[id^="Details-"] summary')
initDisclosureWidgets(summaries)

utils.js ​

Path: theme/frontend/lib/utils.js

fetchConfig(type) ​

Returns a config object for fetch() POST requests with JSON headers.

ParameterTypeDefaultDescription
typestring'json'Accept header media subtype

Returns:

js
{
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json'
  }
}
js
const response = await fetch(window.routes.cart_change_url, {
  ...fetchConfig(),
  body: JSON.stringify({ line, quantity }),
  signal: this.controller.signal,
})

debounce(fn, wait) ​

Returns a debounced function that delays invocation until wait ms after the last call.

ParameterTypeDescription
fnFunctionFunction to debounce
waitnumberDelay in milliseconds

Returns: Function

js
constructor() {
  super()
  this.debouncedOnChange = debounce((event) => {
    this.onChange(event)
  }, 300)
}

connectedCallback() {
  this.controller = new AbortController()
  this.addEventListener('change', this.debouncedOnChange, {
    signal: this.controller.signal,
  })
}

Import summary ​

js
// Event system
import { Events, createEvent, listen } from '@/lib/events'

// Accessibility
import {
  getFocusableElements,
  trapFocus,
  removeTrapFocus,
  onKeyUpEscape,
  initDisclosureWidgets,
} from '@/lib/a11y'

// Helpers
import { fetchConfig, debounce } from '@/lib/utils'

Next steps ​