Skip to main content

Introduction

Horizon uses a custom web component architecture built on native Web Components APIs. All components extend from a base Component class that provides powerful features like automatic DOM reference management, declarative event handling, and shadow DOM support.

Core Architecture

Component Lifecycle

All Horizon web components follow the standard Custom Elements lifecycle:
  • connectedCallback() - Called when the component is inserted into the DOM
  • disconnectedCallback() - Called when the component is removed from the DOM
  • updatedCallback() - Called when the component is re-rendered by the Section Rendering API

Base Classes

Horizon provides two fundamental base classes:

Component

Main base class with refs, event handling, and lifecycle management

DeclarativeShadowElement

Handles declarative shadow DOM hydration

Key Features

Automatic Refs Management

Components automatically track elements with ref attributes:
<my-component>
  <button ref="submitButton">Submit</button>
  <input ref="emailInput" type="email" />
</my-component>
class MyComponent extends Component {
  connectedCallback() {
    super.connectedCallback();
    // Refs are automatically populated
    this.refs.submitButton.disabled = false;
    this.refs.emailInput.value = '';
  }
}

Declarative Event Handling

Use the on: attribute syntax for event handling:
<button on:click="handleClick">Click Me</button>
<input on:input="handleInput" />
<form on:submit="handleSubmit"></form>
The event handler methods are automatically called on the closest component instance:
class MyComponent extends Component {
  handleClick(event) {
    console.log('Button clicked!');
  }
  
  handleInput(event) {
    console.log('Input value:', event.target.value);
  }
  
  handleSubmit(event) {
    event.preventDefault();
    // Handle form submission
  }
}

Event Data Passing

Pass data to event handlers using query string syntax:
<!-- Pass single value -->
<button on:click="handleAction?/3">Delete Item 3</button>

<!-- Pass multiple parameters -->
<button on:click="handleAction?id=123&type=delete">Delete</button>
handleAction(data, event) {
  console.log(data); // 3 or {id: "123", type: "delete"}
}

Component Categories

Horizon’s web components are organized into functional categories:

Cart Components

Components that manage cart functionality:
  • cart-items-component - Displays and manages cart items
  • cart-quantity-selector-component - Quantity selection in cart
  • cart-drawer-component - Slide-out cart drawer
  • cart-icon - Cart icon with item count
  • cart-note - Customer cart notes
  • cart-discount-component - Discount code management
Learn more about cart components →

Product Components

Components that power product pages and product cards:
  • product-form-component - Main add-to-cart form
  • add-to-cart-component - Add to cart button container
  • variant-picker - Product variant selection
  • product-price - Dynamic price display
  • product-inventory - Stock level display
  • quantity-selector-component - Quantity input with validation
Learn more about product components →

Event System

Horizon uses custom events for cross-component communication:

Theme Events

Global events dispatched on document:
import { ThemeEvents, CartUpdateEvent } from '@theme/events';

document.addEventListener(ThemeEvents.cartUpdate, (event) => {
  console.log('Cart updated:', event.detail);
});
Key theme events:
  • ThemeEvents.cartUpdate - Cart contents changed
  • ThemeEvents.cartAdd - Item added to cart
  • ThemeEvents.variantUpdate - Product variant changed
  • ThemeEvents.variantSelected - Variant option selected
  • ThemeEvents.discountUpdate - Discount code applied/removed
  • ThemeEvents.quantitySelectorUpdate - Quantity changed

Defining Custom Components

To create a new component:
import { Component } from '@theme/component';

class MyComponent extends Component {
  // Specify required refs (optional)
  requiredRefs = ['submitButton', 'input'];
  
  connectedCallback() {
    super.connectedCallback();
    // Initialize component
  }
  
  disconnectedCallback() {
    super.disconnectedCallback();
    // Cleanup
  }
}

// Register the component
if (!customElements.get('my-component')) {
  customElements.define('my-component', MyComponent);
}

TypeScript Support

Components support TypeScript generics for type-safe refs:
type Refs = {
  submitButton: HTMLButtonElement;
  emailInput: HTMLInputElement;
  items: HTMLElement[]; // Array refs
};

class MyComponent extends Component<Refs> {
  connectedCallback() {
    super.connectedCallback();
    // this.refs is fully typed
    this.refs.submitButton.disabled = false;
    this.refs.emailInput.value = '';
  }
}

Best Practices

connectedCallback() {
  super.connectedCallback(); // Required!
  // Your code here
}
class MyComponent extends Component {
  requiredRefs = ['dialog', 'closeButton'];
  // Component will throw error if these refs are missing
}
connectedCallback() {
  super.connectedCallback();
  document.addEventListener('scroll', this.handleScroll);
}

disconnectedCallback() {
  super.disconnectedCallback();
  document.removeEventListener('scroll', this.handleScroll);
}
#abortController = new AbortController();

connectedCallback() {
  super.connectedCallback();
  fetch('/api/data', { signal: this.#abortController.signal });
}

disconnectedCallback() {
  super.disconnectedCallback();
  this.#abortController.abort();
}

Next Steps

Component Base Classes

Learn about Component and DeclarativeShadowElement

Cart Components

Explore cart-related web components

Product Components

Discover product page components

Build docs developers (and LLMs) love