Organization DepotDevelopers v1.3.0API 2026-12
All pages

Web components reference

Everything components.js 1.3.0 offers: five custom elements that put products, a cart and checkout on any HTML page. They're built on storefront.js, which is bundled in.

<script
  type="module"
  src="https://developers.organizationdepot.com/storefront/v1/components.js"
></script>
<od-store token="odpk_YOUR_PUBLIC_TOKEN"></od-store>

<od-cart-button></od-cart-button>
<od-product handle="walnut-end-grain-board"></od-product>
  • One ES module, about 19.3 kB gzipped, with no other dependencies. Loading it registers the elements. It exports nothing; the elements are the interface.
  • Evergreen browsers from 2023 on: Chrome and Edge 111, Firefox 113, Safari 16.4.
  • Every element renders in an open shadow root. Its events bubble and cross shadow roots, so you can listen on document.
  • Loading components.js twice (two tags, two versions) is harmless: the first copy's elements win.

How the elements work together

  • <od-store> is the connection. It makes the page's one client for its token (two <od-store>s with the same token share it), loads the shop and applies its branding.
  • Every other element finds its store by its store="id" attribute, else the <od-store> it sits inside, else the first one on the page. Elements may come before the store, and the store may get its token later: they wait, then load. An element that still finds no store after 3 seconds says so in the console.
  • One cart per storefront per browser. Every cart button and drawer on the page shows the same cart and stays in sync, with changes made in another tab and with a return from checkout through the Back button.
  • Checkout is hosted. The drawer's Checkout sends the buyer to your checkout domain; after paying, the cart starts fresh.
  • Text is text. Product, option, detail and personalization values are rendered as text, never as HTML.
  • Strict Content-Security-Policy works. Styles are constructable stylesheets, which style-src doesn't block; there are no inline style attributes and no eval. See Security.

od-store

The page's connection to one storefront. It renders nothing itself; its children render as usual.

Attribute Default What
token required Your token, odpk_… or odpv_… (preview)
api-url https://api.organizationdepot.com/api The API's address. Local backend: http://localhost:12001/api
api-version 2026-12 The Storefront API version
no-branding off Don't apply the shop's branding; style everything yourself
Property What
client The shared storefront.js client (shop, products, cart, on); null without a token
shop The shop once loaded, else null
Event detail When
od:load { shop } The shop loaded and its branding was applied
od:error { error } The shop couldn't load (a StorefrontError). A refused token is also explained in the console
const store = document.querySelector("od-store");
const cart = await store.client.cart.get(); // the same cart the elements show

od-product

A whole product block: photos that follow the photo option (choose Maple, see the maple board), the title and price for the chosen variant, options as buttons (size groups such as Youth and Adult labelled), "Sold out", the description, personalization fields, quantity, Add to cart, and the details (properties) as a list.

Attribute Default What
handle required The product's handle (its slug). Changing it loads that product
heading-level 2 The title's heading level, 1 to 6 (use 1 on a product page); Details is one lower
store The id of the <od-store> to use, when the page has several
Property What
product The loaded Product, or null (loading, not found, failed)
variant The selected Variant; undefined when that combination isn't sold
selectedOptions The buyer's choice, { [optionName]: value }
handle Reflects the attribute
Event detail When
od:load { product } The product loaded
od:variant-change { product, variant, selectedOptions } The buyer's choice names a different variant
od:added { cart, product, variant, quantity, customizations } Add to cart succeeded
od:error { error } Loading or adding failed (the block shows its own message too)

How it behaves:

  • Personalization fields come from the product's form: text (with a character counter when it has a limit), number, choice and checkbox. Required answers and limits are checked before sending; the server checks again, and its CUSTOMIZATION_INVALID message appears on the field it names. After a successful add the fields clear, so the next item can carry a different name.
  • Add to cart is disabled for a sold-out variant, a combination that isn't sold, and while the shop's sale is closed (shop.sale.availableForSale is false). A closed sale comes first, and the button says why, by shop.sale.reason: "Ordering opens soon" (NOT_OPEN_YET), "Ordering paused" (PAUSED), "Sale ended" (ENDED), else "Not on sale right now". With a preview token on <od-store> (shop.preview), the sale doesn't disable it: preview carts work whatever the sale says and pay only in test mode. It reads "Adding…" and ignores clicks while its request runs. The quantity is capped at the product's purchase limit and the stock left.
  • "Sold out" (the badge and the button) comes from stock only: a variant's or product's availableForSale. A closed sale never marks products sold out.
  • Errors read as sentences: "Too many requests right now. Try again in 20 seconds." (RATE_LIMITED), the server's message for OUT_OF_STOCK, PURCHASE_LIMIT and SALE_CLOSED, "This product isn't available." for an unknown handle, and a Try again button when loading fails.
Slot Default
add-label "Add to cart"
sold-out-label "Sold out" (the button)
personalization-label "Personalize it"
details-label "Details"
not-found "This product isn't available."
Part What
root, gallery, info The layout: gallery beside info when the element is 44rem wide or more, stacked below
media, image The main photo
thumbnails, thumbnail (+ selected) The photo picker (two or more photos)
title, price-row, price, description
badge (+ sold-out) The "Sold out" badge
options, option, option-label Each option (a fieldset) and its legend
option-group, option-group-label A cluster of values and its label ("Youth")
option-value (+ selected, unavailable) A value's button (a photo tile when every value has a photo)
personalization, personalization-label The personalization fieldset
field, field-label, input (+ invalid), field-help, field-count, field-error One field
buy, quantity, quantity-button, quantity-input The quantity stepper
add-button (+ busy) Add to cart
message, added, view-cart-button, add-error After adding: the confirmation (with View cart) or the error
details, details-label, detail, detail-label, detail-value The details list
placeholder, not-found, error, retry-button Loading, an unknown handle, a failed load

od-product-list

A grid of product cards: cover photo, title, price ("From $34.00" when variants differ) and "Sold out", a page at a time with Load more.

Attribute Default What
href none Each card's link, with {handle} (and {id}) filled in, URL-encoded: /products/{handle}. Without it, cards aren't links
tag all Only products with this tag (tags are collections)
first 12 Page size, 1 to 100
store The id of the <od-store> to use
Property What
products The products shown so far
Event detail When
od:load { products, pageInfo } After each page loads
od:error { error } A page couldn't load

Load more moves focus to the first new card and announces how many were added.

Slot Default
load-more-label "Load more"
empty "No products to show yet."

Parts: grid, card (+ sold-out; an a with href, else a div), card-media, card-image, card-badge, card-title, card-price, load-more (+ busy), empty, placeholder, error, retry-button.

od-cart-button

A button with the cart's item count that opens the page's <od-cart>. A page without one gets one added to <body>. The count is part of the button's name ("Cart, 3 items") and is announced when it changes; the button sets aria-expanded while the drawer is open.

Attribute What
store The id of the <od-store> to use
Property What
count The number of items (0 until it has loaded)

The default slot is the label ("Cart"): <od-cart-button>Basket</od-cart-button>.

Parts: button, icon, label, count (the badge, hidden at 0).

od-cart

The cart drawer, a modal dialog: focus moves in and stays in, Escape or the backdrop closes it, focus returns to what opened it, and the page behind doesn't scroll. Each line has its photo, title, options, personalization, a quantity stepper, Remove and its price; then the subtotal and Checkout. It follows every change to the cart, from any element or from your own code. A preview cart (made with an odpv_… token) shows "Preview cart: checkout takes test cards only" above Checkout; checkout then takes Stripe's test cards and places no order.

Attribute What
open Open or close it (reflects)
open-on-add Open whenever something is added (od:added)
product-href Link each line's title to its product: /products/{handle} ({id} works too)
store The id of the <od-store> to use
Method / property What
show() Open it
close() Close it
toggle() Open or close it
open Whether it's open (settable)
Event detail When
od:cart-open { cart } Opened
od:cart-close { cart } Closed
od:checkout { cart } Checkout was pressed, before leaving the page. Cancelable: preventDefault() stays here
od:error { error } An edit, loading the cart, or checkout failed (shown in the drawer too)
// Hold checkout until the buyer has accepted your terms, say:
document.addEventListener("od:checkout", (event) => {
  if (!buyerAgreedToTerms()) event.preventDefault();
});
Slot Default
title "Your cart"
empty "Your cart is empty."
note "Tax and fees are calculated at checkout."
checkout-label "Checkout"

Parts: dialog, backdrop, panel, header, title, close-button, body, empty, placeholder, lines, line (+ busy), line-media, line-image, line-info, line-title, line-options, line-personalization, quantity, quantity-button, quantity-value, remove-button, line-price, line-unit-price, footer, subtotal, subtotal-label, subtotal-amount, note, preview-note, message, checkout-button (+ busy), retry-button.

Styling

How theming works for every Organization Depot element is in Embedding and theming. These are the Storefront elements' properties, parts and slots.

CSS custom properties

<od-store> sets your shop's branding on the page as the first seven properties below, with no specificity: any rule of yours wins, on :root or on an element.

Property From branding Default Used for
--od-color-brand color.brand #111827 Buttons, selected options, accents
--od-color-surface color.surface #fff The drawer's panel, badges
--od-color-text color.text the page's text colour (the drawer: #111827) Text
--od-font-heading font.heading --od-font-body Titles
--od-font-body font.body the system font Everything else
--od-radius-base radius.base 10px Corners
--od-density density 1 (compact 0.85, spacious 1.2) Spacing
--od-color-on-brand black or white, by contrast Text on brand-coloured buttons
--od-color-focus the brand colour, darkened Focus rings
--od-color-danger #b42318 Errors
--od-color-backdrop rgb(17 17 17 / .45) Behind the drawer
--od-drawer-width 26rem (full width on phones) The drawer
--od-card-min-width 10.5rem The narrowest a product card gets

Tinted surfaces (option buttons, inputs, steppers) are your text colour at low opacity, so the elements fit light and dark pages alike. Text on a brand-coloured button is worked out from the brand colour in effect, yours or the shop's; set --od-color-on-brand to choose it yourself. A font.* of "Inter" applies when your page (or the device) has Inter: components.js loads no fonts.

:root {
  --od-color-brand: #7c4a1e;
  --od-radius-base: 4px;
}
od-cart {
  --od-drawer-width: 30rem;
}

Parts

Every meaningful piece has a part name, listed per element above. A state is an extra part name, matched together with the base one:

od-product::part(add-button) {
  min-height: 3.5rem;
}
od-product::part(option-value selected) {
  outline: 2px solid currentColor;
}
od-product-list::part(card sold-out) {
  opacity: 0.6;
}
od-cart::part(panel) {
  border-radius: 16px 0 0 16px;
}

Slots

Labels can be replaced with your own markup, by slot name:

<od-product handle="walnut-end-grain-board"><span slot="add-label">Add to basket</span></od-product>
<od-cart><span slot="checkout-label">Pay securely</span></od-cart>
<od-cart-button>Basket</od-cart-button>

Planned: markup templates

Replacing an element's markup with your own, from a <template> child, is planned and not yet in any release. For now, customize with the custom properties, parts and slots above, or build your own markup on storefront.js (the store's client gives you the same cart).

TypeScript

The elements and their events are typed for document.querySelector("od-product") and addEventListener("od:added", …) in the source, but no declarations ship for components.js in 1.0. Treat the elements as HTMLElement and the events as CustomEvent, with the detail shapes above and the types from storefront.js.