# Component API Reference

This reference is optimized for developers and agents using `@orderlyshop/web-components` in vanilla HTML, JavaScript, and TypeScript projects.

Machine-readable metadata is also shipped:

- `custom-elements.json` for custom element tooling and agents.
- `html-custom-data.json` for VS Code HTML autocomplete.

For agents, this reference is the API contract. Use exact names from this file or the machine-readable metadata instead of inventing attributes, template ids, event names, or CSS hooks. Keep generated markup minimal: use attributes for primitive configuration, typed JavaScript properties for Core contracts, and templates only when the shop needs custom rendering.

## Common Conventions

- Core contracts such as `SearchQuery`, `SearchObject`, `DraftOrder`, and `OrderlyClient` are assigned as JavaScript properties.
- Primitive configuration is exposed as attributes.
- Default rendering uses light DOM and stable `orderly-*` classes.
- `defineOrderlyWebComponents()` installs package-owned baseline CSS, so default components have usable spacing, layout, responsive behavior, and product presentation without a shop stylesheet.
- Start theming from [`DESIGN.md`](../../DESIGN.md). Override semantic tokens such as `--orderly-color-primary`, `--orderly-action-primary-*`, `--orderly-action-secondary-*`, `--orderly-selection-*`, `--orderly-badge-*`, `--orderly-radius-card`, `--orderly-radius-panel`, `--orderly-radius-button`, `--orderly-radius-input`, `--orderly-radius-icon-button`, `--orderly-product-tile-*`, `--orderly-product-rail-*`, `--orderly-collection-hero-*`, and `--orderly-checkout-*` before reaching for component-local selectors.
- Package fallback tokens use `:where(:root)`, so shop tokens declared on `:root` win even if package CSS is loaded later.
- Templates use `data-orderly-template`, `data-orderly-bind`, `data-orderly-slot`, and `data-orderly-action`.
- Shared document-level templates can use `data-orderly-for="product-tile"` or another component name so one shop template registry can serve every page.
- Responsive templates can use `data-orderly-viewport="mobile"`, `data-orderly-viewport="desktop"`, or `data-orderly-media`.
- `base-url` configures the backend when no `client` property is supplied.
- Built-in labels and messages are Danish by default. Set `configureShop({ uiLanguage: "EN" })` for English defaults; explicit attributes, typed properties, and templates still override those strings.
- `configureShop({ consent })` installs the package-owned cookie consent UI/runtime. Optional categories default to off, visitors can accept, reject, customize, and later reopen preferences, and configured first-party cookies are deleted when their category is rejected or withdrawn. Custom scripts should use `hasCookieConsent()`, `setOrderlyCookie()`, or `openCookieConsentPreferences()` instead of writing optional cookies directly.
- `configureShop({ analytics })` can install Google Tag Manager and push GA4-compatible data layer events for package-owned navigation, product, basket, checkout, and payment success flows. When package cookie consent is configured, analytics waits for the configured consent categories and queues Google Consent Mode v2 default/update commands. The analytics hooks listen to the documented `orderly-*` DOM events, so shops can also integrate their own analytics listeners without replacing templates.

### Cookie Consent API

`configureShop({ consent })` accepts:

- `enabled`: set `false` to disable a checked-in config without deleting it.
- `version`: consent record version. Change it when purposes, vendors, or the cookie policy materially change.
- `policyHref`: cookie/privacy policy link shown in the first layer.
- `categories`: category definitions. `necessary` is always required; package defaults are `necessary`, `preferences`, `statistics`, and `marketing`.
- `managedCookies`: first-party cookies the package should disclose in preferences and delete when rejected. Names can be exact or simple wildcard patterns such as `_ga_*`.
- `texts`: override Danish default copy.
- `autoShow`, `showPreferencesButton`, and `position`: tune when and where the UI appears.
- `onChange`: called with the saved `CookieConsentState`.

Exported helpers: `currentCookieConsent()`, `hasCookieConsent(category)`, `saveCookieConsent(categories)`, `openCookieConsentPreferences()`, `setOrderlyCookie(name, value, { category })`, `deleteOrderlyCookie(name)`, `clearCookiesForCategory(category)`, and `addCookieConsentListener(listener)`. The package dispatches `orderly-cookie-consent-change` on `document` whenever a choice is saved.

Consent configuration is compliance support, not a complete legal document. The consuming shop must keep `managedCookies`, cookie policy text, vendors, durations, and any server-side or third-party deletion accurate for its own implementation.

## Design Contract

The package design system is token-based. The expected workflow is:

1. Set the shared theme in [`DESIGN.md`](../../DESIGN.md).
2. Verify that primary actions, secondary actions, badges, links, and selection states all follow the same palette.
3. Verify that product tiles, checkout cards, basket/order rows, inputs, buttons, and header icon buttons share one radius system.
4. Only then use component-specific styling hooks such as navigation or product-grid variables for intentional per-component differences.

Legacy `--orderly-color-accent` variables are still supported as compatibility aliases, but new themes should treat `--orderly-color-primary` as the package-wide source of truth.
Legacy `--orderly-radius` and `--orderly-icon-button-radius` variables are also supported, but new themes should use the semantic radius tokens from `DESIGN.md`.

## Declarative Query Format

`orderly-product-grid`, `orderly-product-rail`, and `orderly-collection-page` support simple declarative search query markup.

Read [`../search-syntax.md`](../search-syntax.md) before writing category or rail queries. `keywords` / `SearchQuery.Query` uses backend text search where whitespace narrows the query; use `|` for OR alternatives such as `lego | matchbox | playmobil`. Multiple `tags` are narrowing AND-style filters, not broad alternatives.

Attributes:

- `keywords`: maps to `SearchQuery.Query`.
- `tags`: comma-separated values mapped to `SearchQuery.Tags`.
- `hidden-query`: maps to `SearchQuery.HiddenQuery`.
- `order-by`: comma-separated values mapped to `SearchQuery.OrderBy`.
- `store-id`: maps to `SearchQuery.StoreId`.
- `featured`: boolean mapped to `SearchQuery.Featured`.

Example:

```html
<orderly-product-rail
  title="Børnetøj"
  keywords="hummel | småfolk | name it"
  tags="børnetøj"
  order-by="CreatedTime desc">
</orderly-product-rail>
```

Equivalent child markup:

```html
<orderly-product-rail title="Børnetøj">
  <query>
    <tags>børnetøj</tags>
    <keywords>hummel | småfolk | name it</keywords>
    <order-by>CreatedTime desc</order-by>
  </query>
</orderly-product-rail>
```

For the full Core query contract, assign the typed `query` property from JavaScript.
Vanilla browser scripts that use the standalone bundle can create typed queries with `OrderlyWebComponents.createSearchQuery({ query, tags, orderBy })`; module-based shops can import the same helper from `@orderlyshop/web-components/query`.

## Global Template Registry

Local templates placed inside an element take precedence. If no local template matches, components look for global templates in the document and in the JavaScript registry.

```html
<template data-orderly-template="product" data-orderly-for="product-tile">
  <article class="product-card">
    <h3 data-orderly-bind="title"></h3>
    <orderly-stored-image data-orderly-bind="image" fit="contain"></orderly-stored-image>
    <button type="button" data-orderly-action="add-to-basket">
      <span data-orderly-bind="basket-action-icon"></span>
    </button>
  </article>
</template>
```

Use `data-orderly-for` with a full custom element name, a prefix-independent component name such as `product-tile`, or `*`. Global templates support `data-orderly-viewport` and `data-orderly-media`.

## Components

### `orderly-page-layout`

Reusable page structure with slots for `utility`, `header`, `header-actions`, `primary-nav`, `left`, `right`, `content-before`, `content`, `content-after`, `footer`, and `overlay`.

After footer slot/template content, the layout always appends the package-owned legal ownership notice: `Webshoppen ejes af <NAVN> cvr: <CVR> og bestyres af Orderly ApS cvr: 42410896`. Configure `<NAVN>` and `<CVR>` through `defaultShop.legalOwnerName` and `defaultShop.legalOwnerCvr`; the Orderly text links to `https://orderly.shop`. Shop templates cannot override this notice.

The default layout can move `primary-nav` responsively without a custom layout template. Use `primary-nav-mobile-placement="header"` to place a burger navigation inline before the logo/header content on mobile, and keep the same slotted navigation below the header on desktop. For category pages that need a side menu on desktop and a header burger on mobile, use `primary-nav-desktop-placement="left" primary-nav-mobile-placement="header"`.

Attributes:

- `logo-src`
- `logo-alt`
- `logo-href`
- `primary-nav-placement`: `primary-nav` (default), `header`, `left`, or `right`
- `primary-nav-desktop-placement`
- `primary-nav-mobile-placement`

Templates:

- `layout`

### `orderly-category-page`

Default shop category page that composes layout, navigation, search, collection, product grid, basket, product detail, and footer.

Package-owned default pages use `defaultShop.navigationLayout` when they compose `orderly-page-layout` and `orderly-navigation`. Set it through `configureShop({ navigationLayout })` or `configureShop({ defaultShop: { navigationLayout } })` to choose top horizontal category navigation on desktop/tablet and a burger menu in the mobile header without replacing category, product, checkout, or page-layout templates.

Attributes:

- `base-url`
- `slug`
- `brand-label`
- `product-href`
- `checkout-href`
- `checkout-label`
- `search-placeholder`
- `empty-label`
- `basket-mode`: use `inline-desktop` to render the package-owned basket as a right rail on desktop when it has lines. Mobile still uses the drawer.

Properties:

- `client`
- `defaultQuery`
- `query` through child grid/collection composition
- `category`
- `navigationItems`
- `sortOptions`
- `basketController`

Templates:

- `layout`
- `empty-category`

`orderly-category-page` can resolve its category from generated path URLs by default, from `slug`, or from a static hash route when a shop opts into hash routing:

```html
<a href="/categories/sko/">Sko</a>
<orderly-category-page></orderly-category-page>

<orderly-category-page slug="sko"></orderly-category-page>
```

`navigationDefinitions` must define an explicit unique `slug` on every item. If the configured navigation is invalid, the component renders a visible category configuration error instead of silently falling back to another category.

When a product is opened in the default product detail popup, the popup pushes a same-URL history entry so the browser back button closes the overlay before leaving the current category. The popup is fullscreen on mobile and a centered dialog on larger viewports, with desktop image size controlled by `--orderly-product-dialog-image-size`. On mobile the popup has a sticky top action row with package-owned search, basket, and close controls; the separate expand button is hidden there. It also includes an icon link to the full product page on larger viewports. It uses `product-href`, defaulting to `/product.html`, and appends the Core `SearchObject.ShareURL.URL` as `#url=...`. For `https://orderly.shop/...` product URLs, the hash value is compacted to the path before encoding.

### `orderly-home-page`

Default storefront homepage that renders one horizontal `orderly-product-rail` for every configured top-level category. It also composes page layout, responsive primary navigation, basket icon, basket drawer, product detail dialog, and footer.

Attributes:

- `base-url`
- `brand-label`
- `eyebrow`
- `title`
- `rail-cta-label`
- `empty-label`
- `product-href`
- `checkout-href`
- `checkout-label`
- `basket-mode`: use `inline-desktop` to render the package-owned basket as a right rail on desktop when it has lines. Mobile still uses the drawer.

Properties:

- `client`
- `defaultQuery`
- `navigationItems`
- `sortOptions`
- `basketController`
- `basketLabels`

Slots:

- `utility`
- `header-actions`
- `hero`: replaces the default homepage intro/hero content while keeping package-owned rails, navigation, basket, and product dialog behavior.
- `content-before`
- `content-after`
- `footer`
- `overlay`

Templates:

- `layout`
- `hero`: replaces the default homepage intro/hero content. Bind `title` and `eyebrow` with `data-orderly-bind` if the custom hero still uses the component attributes.
- `rail`
- `grid`
- `product`
- `loading`
- `empty`

When a product is opened from a homepage rail, the default product detail popup pushes a same-URL history entry so the browser back button closes the overlay before leaving the homepage. The popup is fullscreen on mobile and a centered dialog on larger viewports, with desktop image size controlled by `--orderly-product-dialog-image-size`. On mobile the popup has a sticky top action row with package-owned search, basket, and close controls; the separate expand button is hidden there.

### `orderly-checkout-page`

Default checkout page that composes page layout, responsive primary navigation, checkout form, basket order summary, footer, and shared basket state.

The composed checkout and basket share one `BasketController`. If the persisted draft already contains a complete address, the checkout restores delivery methods when it mounts. If the draft also contains a selected delivery method or service point, the checkout verifies that selection once so the basket summary can show backend-provided shipping and total prices.

Attributes:

- `base-url`
- `brand-label`
- `title`
- `description`
- `order-title`
- `terms-href`

Properties:

- `client`
- `basketController`
- `navigationItems`
- `basketLabels`
- `checkoutLabels`

Slots:

- `utility`
- `header-actions`
- `content-before`
- `content-after`
- `footer`
- `overlay`

Templates:

- `layout`
- `checkout`
- `basket`
- `line`
- `empty`

Submit flow:

- The built-in `orderly-checkout` submit button is disabled until a delivery method is selected and the current `DraftOrder` has no `DraftOrder.Errors` or line errors.
- Submit calls `OrderService.Create(draftOrder)` directly. Pricing and validation must already come from backend `VerifyDraft`; the frontend does not calculate order totals.
- Created orders are stored in localStorage through `OrderStore` so payment callback pages can render receipts from the base64 encoded protobuf `OrderId` query parameter.
- If `Order.Payment.PaymentLink.URL` is present, the checkout opens that payment link in the same browser window.

### `orderly-payment-success-page` and `orderly-payment-failure-page`

Payment callback pages for `/payment-success.html?orderid=...` and `/payment-failure.html?orderid=...`. The `orderid` value is a base64 encoded protobuf `OrderId`. Both pages load the created order from the package order store.

Attributes:

- `order-id`
- `brand-label`
- `title`
- `description`
- `reference-label`
- `order-title`
- `empty-label`
- `retry-label` (`orderly-payment-failure-page`)

Properties:

- `orderStore`
- `navigationItems`

Events:

- `orderly-payment-success`
- `orderly-payment-failure`

Both events include `{ order }` after the stored order has loaded. Package analytics uses `orderly-payment-success` for GA4 `purchase` conversion tracking.

Failure pages render a "Prøv igen" link from `Order.Payment.PaymentLink.URL`.

### `orderly-product-detail-page`

Default product detail page that composes page layout, navigation, product detail, basket drawer, and footer. It resolves the `SearchObject` itself by calling `SearchService.Search` with `SearchQuery.ShareUrl`.

It forwards the product detail extension slots `details-before`, `purchase-note`, `secondary-cta`, and `details-after` to the nested `orderly-product-page`, so a shop can add trust copy or a secondary CTA without replacing the page or product template.

Styling tokens:

- `--orderly-product-detail-page-padding`
- `--orderly-product-detail-page-mobile-padding`
- `--orderly-product-detail-page-inline-padding`
- `--orderly-product-detail-page-mobile-inline-padding`

```html
<orderly-product-detail-page share-url="https://shop.example/products/my-product"></orderly-product-detail-page>
```

For static product routes, omit `share-url` and pass the encoded product share URL in the page hash:

```html
<a href="/product.html#url=https%3A%2F%2Fshop.example%2Fproducts%2Fmy-product">View product</a>
<orderly-product-detail-page></orderly-product-detail-page>
```

Orderly product URLs can use the compact form. The component restores `https://orderly.shop/` before searching:

```html
<a href="/product.html#url=mickey-mouse-billedramme-fra-danish-design-str-40x50-cm-p7yxrjsfbq.html">Se produkt</a>
<orderly-product-detail-page></orderly-product-detail-page>
```

Attributes:

- `base-url`
- `share-url`
- `brand-label`
- `checkout-href`
- `checkout-label`
- `add-label`
- `in-basket-label`
- `loading-label`
- `not-found-label`
- `error-label`
- `basket-mode`: use `inline-desktop` to render the package-owned basket as a right rail on desktop when it has lines. Mobile still uses the drawer.

Properties:

- `client`
- `shareUrl`
- `product`
- `defaultQuery`
- `navigationItems`
- `basketController`
- `basketLabels`

Events:

- `orderly-product-loaded`
- `orderly-product-not-found`
- `orderly-error`

Templates:

- `layout`
- `product`
- `thumbnail`
- `basket`
- `line`
- `empty`

### `orderly-product-grid`

Search-backed product listing with sort controls, continuation-token paging, loading states, responsive full-width tracks, capped tile widths, and template overrides.

Detailed reference: [product-grid.md](./product-grid.md)

### `orderly-product-rail`

Horizontal product rail for homepages and editorial rows. It composes `orderly-product-grid`.

Detailed reference: [product-rail.md](./product-rail.md)

### `orderly-collection-page`

Collection hero plus product grid. The default hero stretches across the available page width and centers its intro content. Set `hero-layout="split"` or `hero-layout="covers"` and `hero-cover-count` to make the hero use product cover previews from the loaded category products.

Attributes:

- `base-url`
- `title`
- `description`
- `hero-image`
- `hero-layout`: `image` (default), `split`, or `covers`
- `hero-cover-count`
- `cta-label`
- `cta-href`
- Declarative query attributes: `keywords`, `tags`, `hidden-query`, `order-by`, `store-id`, `featured`

Properties:

- `client`
- `query`
- `defaultQuery`
- `basketController`
- `sortOptions`

Templates:

- `layout`
- `hero`
- `grid`
- `product`
- `loading`
- `empty`

### `orderly-product-tile`

Product card for `SearchObject`.

Default product cards include `schema.org/Product`, nested `schema.org/Brand`, and `schema.org/Offer` microdata for Google product snippets. Custom templates should preserve the `data-orderly-bind="schema-*"` placeholders plus the `data-orderly-schema-brand` and `data-orderly-schema-offer` wrappers when they replace the default product card markup.

`data-orderly-bind="share-url"` keeps the raw `SearchObject.ShareURL.URL` on plain text elements and on anchors without an existing `href`. If an anchor already points at a product page, the package rewrites it to the configured physical product route using the active hash- or path-based product URL mode.

Keyboard focus is indicated on the product title instead of drawing a frame around the whole tile. Custom product tile templates should keep a title element with `data-orderly-bind="title"` or `.orderly-product-tile__title` so the default focus indicator still has a target.

Attributes:

- `add-label`
- `remove-label`

Properties:

- `product`
- `basketController`

Events:

- `orderly-product-selected`
- `orderly-add-to-basket`
- `orderly-remove-from-basket`

Templates:

- `product`
- `default`

### `orderly-product-page`

Product detail view with thumbnail gallery and add-to-basket action.

Styling tokens:

- `--orderly-product-page-gap`
- `--orderly-product-page-mobile-gap`
- `--orderly-product-page-media-gap`
- `--orderly-product-page-padding`
- `--orderly-product-page-mobile-padding`
- `--orderly-product-page-details-padding`
- `--orderly-product-page-mobile-details-padding`
- `--orderly-product-page-image-border`
- `--orderly-product-page-image-radius`
- `--orderly-product-page-image-background`
- `--orderly-product-page-thumbnail-border`
- `--orderly-product-image-overlay-gap`
- `--orderly-product-image-overlay-padding`
- `--orderly-product-image-overlay-thumbnail-size`
- `--orderly-product-image-overlay-frame-size`

The default main image frame is styled on the outer image button wrapper. The inner `orderly-stored-image` stays borderless so shops can wrap or replace the image area without fighting an extra package-owned inner frame.

The fullscreen image viewer is package-owned. Custom `product` templates only need inline image markup such as `data-orderly-bind="image"` plus an optional `data-orderly-slot="thumbnails"`. `orderly-product-page` injects and controls the overlay itself, including close, click/wheel zoom, overlay thumbnails, labels, focus, keyboard behavior, browser history handling, and a square image viewport sized as large as the current viewport allows. Use `image-viewer="false"` to opt out. Opening the viewer pushes a same-URL history entry, so browser back closes the image viewer before leaving the product page or product dialog. When the product page is rendered inside an open product `<dialog>`, the image viewer is portaled into that dialog so it appears above the product overlay; otherwise it is portaled to `document.body`. Custom CSS and tests should target `[data-orderly-image-overlay]` and should not assume the overlay remains inside the `orderly-product-page` host while open.

The default product details template exposes extension slots for shop-specific content that should not require replacing the whole product template:

- `details-before`
- `purchase-note`
- `secondary-cta`
- `details-after`

`orderly-product-detail-page` forwards the same slot names to its nested `orderly-product-page`, so page users can place these children directly on either component. For default shop-wide content, use `configureShop({ defaultShop: { productDetail: { trustNote: { title, text }, secondaryCta: { label, href } } } })`. The package renders these blocks into `purchase-note` and `secondary-cta` only when those slots are empty.

Attributes:

- `add-label`
- `in-basket-label`
- `image-viewer`

Properties:

- `product`
- `selectedImageIndex`
- `basketController`

Events:

- `orderly-add-to-basket`
- `orderly-product-viewed`
- `orderly-product-image-selected`

Templates:

- `product`
- `thumbnail`

Product template bindings:

- `title`
- `description`
- `brand`
- `size`
- `color`
- `condition`
- `price`
- `image`
- `addLabel`
- `closeLabel`
- `basket-action-label`
- `basket-action-icon`
- `basket-action-state`

`addLabel` is the existing product-page button label binding. `basket-action-label`, `basket-action-icon`, and `basket-action-state` are supported as compatibility aliases for shops that share add-to-basket button markup with `orderly-product-tile`; `basket-action-label` switches from `add-label` to `in-basket-label` after the product is added, and `basket-action-state` is `not-in-basket` or `in-basket`.

Product template actions:

- `data-orderly-action="add-to-basket"` adds the current product to the assigned `basketController`

Optional image viewer trigger hook:

- `data-orderly-image-viewer-trigger` marks a custom wrapper as the click/keyboard trigger for the package-owned fullscreen image viewer. When no explicit trigger is present, the component automatically uses the inline element bound with `data-orderly-bind="image"`.

Thumbnail template bindings and actions:

- `thumbnail`
- `image`
- `index`
- `data-orderly-action="select-image"`

### `orderly-credit`

Formats and renders a Core `Credit` value as stylable money markup. DKK values are formatted as `kr. <pris>`, for example `kr. 100,00`.

```html
<orderly-credit amount="100" currency="DKK"></orderly-credit>
```

Properties:

- `credit`
- `amount`
- `currency`
- `locale`
- `emptyLabel`

Attributes:

- `amount`
- `currency`
- `locale`
- `empty-label`

Templates:

- `credit`
- `default`

### `orderly-stored-image`

Renders `StoredImage` with configured URL prefix, rotation, crop, fit handling, and role-based CDN sizing. The component is borderless by default. Add `border` when a framed image is desired.

Image URLs use configured roles instead of measuring rendered layout. Default roles are `product-tile` at 500x500, `product-detail` at 900x900, `thumbnail`, `basket-line`, and `checkout-line` at 180x180, and `fullsize` with no `width`/`height` parameters. Set `image-role` on the component, pass `{ role }` to `storedImageUrl()`, or override role sizes with `configureShop({ storedImageUrls: { roles } })`. Custom `configureStoredImageUrls({ resolver })` functions receive `{ variant, role, width, height }` as the second argument and should preserve those dimensions in the returned URL when the target CDN supports resizing.

Attributes:

- `fit`
- `variant`
- `image-role`
- `alt`
- `loading`
- `decoding`
- `fetchpriority`
- `border`

Properties:

- `image`

Templates:

- `image`

### `orderly-basket-icon`

Basket button with item count badge.

Attributes:

- `label`
- `href`: optional checkout/cart URL. When set, the default control renders as an anchor and emits `orderly-basket-checkout` instead of opening the basket drawer.
- `placement`: use `mobile-fixed` for a package-owned fixed mobile cart/checkout button.
- `hide-when-empty`

Properties:

- `basketController`

Events:

- `orderly-basket-open`
- `orderly-basket-change`
- `orderly-basket-checkout`

Both events use the current `DraftOrder` as `event.detail`. Derive item count from `event.detail.OrderLines` instead of relying on a separate basket data shape.

Basket count contract:

- `BasketController.count` is the canonical package-owned count and is derived from `DraftOrder.OrderLines[].Quantity`.
- Basket product identity is `SKU.Value` only. `BasketController.hasProduct()`, add/remove toggles, and product-page basket state compare `SearchObject.SKU.Value` with `DraftOrderLine.SKU.Value`; they do not match by title, barcode, target id, or share URL.
- `orderly-basket-icon` follows the assigned `basketController` and rerenders its badge after `load`, add, remove, quantity, clear, set-draft, and verify changes.
- `orderly-basket-change` and `orderly-basket-verified` expose the current `DraftOrder` directly as `event.detail`; use `draftOrderItemCount(detail)` or `event.detail.OrderLines.reduce((sum, line) => sum + (line.Quantity ?? 0), 0)` when writing custom UI.
- Persisted baskets should be loaded through `BasketController.load()` or by assigning the same controller to `orderly-basket`, `orderly-basket-icon`, product grids/rails, and checkout. Do not mirror basket state into separate shop-owned globals.

Templates:

- `basket-icon`
- `icon`

### `orderly-basket`

Basket overview backed by `DraftOrder`.

When a client or `base-url` is configured, the component verifies non-empty persisted drafts on load and later basket mutations through `OrderService.VerifyDraft`. Basket summary values come only from backend-provided `DraftOrder` fields; the component no longer derives subtotal or total from line-item math in the browser. It also renders both order-level `DraftOrder.Errors` and line-level `DraftOrderLine.Errors`.

Attributes:

- `base-url`
- `empty-label`
- `clear-label`
- `continue-label`
- `continue-href`
- `quantity-label`
- `remove-label`
- `subtotal-label`
- `shipping-label`
- `total-label`
- `total-note`
- `delivery-note`
- `checkout-href`
- `checkout-label`
- `checkout-action`

Properties:

- `client`
- `basketController`

Events:

- `orderly-basket-change`
- `orderly-basket-verified`
- `orderly-basket-checkout`

`orderly-basket-change` and `orderly-basket-verified` use the current `DraftOrder` as `event.detail`. `orderly-basket-checkout` includes `{ draft, count, href }` when the checkout action is triggered. Basket components do not expose a duplicated internal basket data type.

Templates:

- `basket`
- `line`
- `empty`

Default template slots:

- `lines`
- `errors`
- `empty`

### `orderly-checkout`

Checkout form for contact/address, delivery methods, service points, draft verification, and order creation.

The default checkout layout hides the address country field and uses `DK` as the default `Address.CountryCodeIso2Letter`. The phone country field renders as a picker with `+45` selected by default, and the default form applies built-in email and phone-number validation without requiring third-party validation libraries. When a Danish 4-digit postal code is entered, the component looks up the city name through Dataforsyningen's `https://api.dataforsyningen.dk/postnumre/{postnr}` endpoint and fills the city field if it is empty or was previously auto-filled.

The default delivery UI follows the Orderly storefront pattern:

- address fields are filled before delivery methods are shown
- delivery methods render as selectable radio cards
- service points render as a separate radio-card list when the selected delivery method requires a pickup point
- selecting a delivery method or service point verifies the draft so basket totals can update immediately
- a persisted complete address restores delivery methods on mount, and a persisted delivery/service-point choice is verified once so shared basket summaries receive backend shipping and total prices

Attributes:

- `base-url`
- `terms-href`
- `delivery-address-prompt-label`
- `no-delivery-methods-label`
- `service-point-help-label`
- `no-service-points-label`

Properties:

- `client`
- `basketController`
- `labels`

Events:

- `orderly-checkout-ready`
- `orderly-checkout-delivery-selected`
- `orderly-order-created`
- `orderly-error`

Templates:

- `checkout`

Default template slots:

- `deliveryMethods`
- `servicePoints`

Checkout Responsive Overrides:

- Keep the package breakpoint in mind when overriding checkout layout. The default CSS collapses `.orderly-checkout-page__grid`, `.orderly-checkout__field-grid`, `.orderly-checkout__delivery-grid`, and `.orderly-checkout__option-grid--delivery` inside the package mobile media query.
- Prefer token and spacing overrides on `.orderly-checkout-page`, `.orderly-checkout-page__card`, `.orderly-checkout`, `.orderly-checkout__field-grid`, and `.orderly-checkout__option-grid` instead of hard-coding desktop `grid-template-columns` outside a media query.
- If a shop needs custom desktop columns, scope them with `@media (min-width: 768px)` or the same breakpoint passed to `configureShop({ responsiveTemplates: { mobileMediaQuery } })`.
- Do not set `display: contents` or fixed widths on `.orderly-checkout-page__card--summary`; the package moves checkout submit/terms controls into the summary card and needs that card to remain a normal layout container.

### `orderly-navigation`

Navigation menu backed by `NavigationController`.

In vertical mode, items with children expand on first click and navigate/select on the next click. In horizontal desktop mode, top-level and second-level links navigate/select on click when they have an `href`, while dedicated toggles control which branch or dropdown is open. In `burgermenu` mode the component renders a compact icon button that opens the same nested disclosure pattern as vertical navigation inside a floating panel, so the menu can live inline in a mobile header or utility bar without taking over the full viewport.

Navigation is sticky by default so side navigation remains visible while category/product content scrolls. Set `sticky="false"` to opt out. The same attribute works when navigation is slotted into `left`, `right`, or `primary-nav`.

Set `layout="horizontal"` to render a desktop navigation bar in the same pattern as `orderly.shop`: choose a top-level item, render the next level as a separate horizontal row, and show the third level as a dropdown from the active second-level item. The top row stays on one line and scrolls horizontally when it overflows instead of wrapping. The second row spans the full available navigation width, including the separator line above it. On narrow viewports the component automatically falls back to the existing drawer-style mobile navigation where the element is placed. This automatic fallback is not the same pattern as moving a compact burger trigger into the page header actions.

Set `layout="burgermenu"` when the menu should start as a single icon button and expand into a floating disclosure panel. This mode keeps the compact trigger for mobile nav bars and inline header actions, but renders child items inline beneath their parent like vertical navigation instead of using the horizontal second-row pattern.

Responsive navigation patterns:

- Use `layout="horizontal"` when the navigation should stay in its normal page position and the component should own the narrow-viewport fallback.
- Use `layout-desktop="horizontal" layout-mobile="burgermenu"` together with `orderly-page-layout primary-nav-mobile-placement="header"` when the desktop site needs a horizontal category bar and the mobile site needs a compact burger beside search, basket, or other header icon buttons.
- Use viewport-specific page-layout templates only when a shop replaces the whole layout and must manually place `data-orderly-slot="primary-nav"` differently on mobile.

The default home/category/product/checkout/payment pages can use this without template replacement:

```ts
configureShop({
  navigationLayout: {
    primaryNavDesktopPlacement: "primary-nav",
    primaryNavMobilePlacement: "header",
    layoutDesktop: "horizontal",
    layoutMobile: "burgermenu"
  }
});
```

In horizontal mode, the active or expanded top-level and second-level categories also render a dedicated indicator line. By default it tracks the text width inside the link padding, and it remains purely presentational and themeable through CSS custom properties.

Attributes:

- `layout`: `vertical` (default), `horizontal`, or `burgermenu`
- `layout-desktop`: desktop override for `layout`
- `layout-mobile`: mobile override for `layout`
- `sticky`
- `persist-state`: persist expanded category ids in `localStorage`, useful for vertical desktop menus that should remember open branches across page navigation.
- `state-key`: storage key suffix for persisted state when a shop has multiple menus.

Properties:

- `items`
- `controller`
- `layout`
- `layoutDesktop`
- `layoutMobile`
- `sticky`

Events:

- `orderly-navigation-select`

Slots:

- `menu-before`: menu-level content before items, such as "Find nærmeste butik".
- `before-items`: inline content before the navigation items. Useful for logos or compact utility content.
- `after-items`: inline content after the navigation items. Useful for marketing copy, badges, or CTA blocks.
- `menu-after`: menu-level content after items, such as "Bliv frivillig", store finder links, or support links.

```html
<orderly-navigation persist-state state-key="main-menu">
  <a slot="menu-after" href="/butikker/">Find nærmeste butik</a>
</orderly-navigation>
```

Templates:

- `navigation`
- `item`

Styling hooks:

- Data attributes:
  - `[data-orderly-layout="horizontal"]`
  - `[data-orderly-layout="burgermenu"]`
  - `[data-orderly-level="0"]`, `[data-orderly-level="1"]`, ...
  - `[data-orderly-has-children]`
  - `[data-orderly-active]`
  - `[data-orderly-expanded]`
- CSS custom properties:
  - `--orderly-navigation-horizontal-padding`
  - `--orderly-navigation-horizontal-inline-padding`
  - `--orderly-navigation-link-padding`
  - `--orderly-navigation-link-padding-horizontal`
  - `--orderly-navigation-link-radius`
  - `--orderly-navigation-link-background`
  - `--orderly-navigation-link-color`
  - `--orderly-navigation-link-hover-background`
  - `--orderly-navigation-link-hover-color`
  - `--orderly-navigation-link-active-background`
  - `--orderly-navigation-link-active-color`
  - `--orderly-navigation-indicator-color`
  - `--orderly-navigation-indicator-height`
  - `--orderly-navigation-indicator-radius`
  - `--orderly-navigation-indicator-inset`
  - `--orderly-navigation-indicator-start-offset`
  - `--orderly-navigation-indicator-end-offset`
  - `--orderly-navigation-indicator-offset`
  - `--orderly-navigation-indicator-collapsed-scale`
  - `--orderly-navigation-child-link-color`
  - `--orderly-navigation-child-link-font-size`
  - `--orderly-navigation-child-link-font-weight`
  - `--orderly-navigation-subnav-padding`
  - `--orderly-navigation-subnav-border-top`
  - `--orderly-navigation-subnav-link-padding`
  - `--orderly-navigation-dropdown-offset`
  - `--orderly-navigation-dropdown-min-width`
  - `--orderly-navigation-dropdown-max-width`
  - `--orderly-navigation-dropdown-border`
  - `--orderly-navigation-dropdown-radius`
  - `--orderly-navigation-dropdown-background`
  - `--orderly-navigation-dropdown-shadow`
  - `--orderly-navigation-megamenu-border`
  - `--orderly-navigation-megamenu-radius`
  - `--orderly-navigation-megamenu-background`
  - `--orderly-navigation-megamenu-shadow`
  - `--orderly-navigation-burgermenu-button-size`
  - `--orderly-navigation-burgermenu-offset`
  - `--orderly-navigation-burgermenu-panel-width`
  - `--orderly-navigation-burgermenu-panel-min-width`

Notes:

- In the default template, the mobile menu header automatically mirrors the nearest page layout branding when `logo-src`, `logo-alt`, and header brand content are defined on `orderly-page-layout`.
- Custom navigation templates can bind `menuBrand`, `menuBrandHref`, `menuBrandImage`, and `menuBrandLabel` if they want to reuse the same branding source.

Example:

```html
<orderly-navigation
  layout="horizontal"
  aria-label="Main navigation"
></orderly-navigation>
```

Compact burgermenu:

```html
<orderly-navigation
  layout="burgermenu"
  aria-label="Main navigation"
></orderly-navigation>
```

Desktop horizontal nav with inline mobile burger:

```html
<orderly-page-layout primary-nav-mobile-placement="header">
  <a slot="header" class="shop-brand" href="/">Shop name</a>
  <orderly-search-box slot="header-actions" mode="icon"></orderly-search-box>
  <orderly-basket-icon slot="header-actions"></orderly-basket-icon>
  <orderly-navigation
    slot="primary-nav"
    layout-desktop="horizontal"
    layout-mobile="burgermenu"
    aria-label="Main navigation"
  ></orderly-navigation>
  <main slot="content">...</main>
</orderly-page-layout>
```

Custom page-layout template fallback when the whole layout is replaced:

```html
<orderly-page-layout>
  <template data-orderly-template="layout" data-orderly-viewport="desktop">
    <section class="shop-layout">
      <header data-orderly-slot="header"></header>
      <nav data-orderly-slot="primary-nav"></nav>
      <main data-orderly-slot="content"></main>
      <footer data-orderly-slot="footer"></footer>
    </section>
  </template>
  <template data-orderly-template="layout" data-orderly-viewport="mobile">
    <section class="shop-layout shop-layout--mobile">
      <header class="shop-header">
        <div data-orderly-slot="primary-nav"></div>
        <div data-orderly-slot="header"></div>
        <div data-orderly-slot="header-actions"></div>
      </header>
      <main data-orderly-slot="content"></main>
      <footer data-orderly-slot="footer"></footer>
    </section>
  </template>

  <a slot="header" href="/">Shop name</a>
  <orderly-search-box slot="header-actions" mode="icon"></orderly-search-box>
  <orderly-basket-icon slot="header-actions"></orderly-basket-icon>
  <orderly-navigation
    slot="primary-nav"
    layout-desktop="horizontal"
    layout-mobile="burgermenu"
  ></orderly-navigation>
  <main slot="content">...</main>
</orderly-page-layout>
```

Category side nav on desktop with the same inline mobile burger:

```html
<orderly-page-layout primary-nav-desktop-placement="left" primary-nav-mobile-placement="header">
  <orderly-navigation
    slot="primary-nav"
    layout-desktop="vertical"
    layout-mobile="burgermenu"
    aria-label="Categories"
  ></orderly-navigation>
  <main slot="content">...</main>
</orderly-page-layout>
```

Inline custom content:

```html
<orderly-navigation layout="horizontal" aria-label="Main navigation">
  <a slot="before-items" href="/" class="shop-nav__logo">
    <img src="/brand/logo.svg" alt="Orderly" />
  </a>

  <aside slot="after-items" class="shop-nav__promo">
    Fri fragt over 499 kr.
  </aside>
</orderly-navigation>
```

Custom item template:

```html
<orderly-navigation layout="horizontal" aria-label="Main navigation">
  <template data-orderly-template="item">
    <li class="shop-nav__item" data-orderly-item>
      <a class="shop-nav__link" data-orderly-action="select">
        <span data-orderly-bind="label"></span>
      </a>
      <button class="shop-nav__toggle" type="button" data-orderly-action="toggle">
        More
      </button>
      <div class="shop-nav__children" data-orderly-slot="children"></div>
    </li>
  </template>
</orderly-navigation>
```

### Search Controls

Search controls bind to a target `orderly-product-grid`.

Components:

- `orderly-search-box`
- `orderly-sort-select`
- `orderly-filter-panel`
- `orderly-load-more`

Common attributes:

- `for`
- `mode`: `textbox` (default) or `icon`. Icon mode renders a search icon and opens a full-page overlay with a search input.
- `placeholder`
- `label`
- `close-label`
- `prompt-label`
- `loading-label`
- `error-label`
- `empty-label`
- `debounce-ms`: debounce delay for automatic search. Defaults to `500`.

Icon mode opens a full-page overlay, immediately renders product results below the search field, and then searches automatically after the debounce delay as the user types. When no search text has been entered yet, the overlay defaults to the target grid's current sort order or the shop's default "latest products" sort. It reuses the target grid's product, loading, and empty templates when available.

Templates:

- `search-box`
- `sort-select`
- `filter-panel`
- `load-more`

### `orderly-shop-footer`

Configurable shop footer with logo, about text, address, contact information, opening hours, and information links.

For shop-owned footer content, prefer a global template in shared HTML:

```html
<template data-orderly-template="footer" data-orderly-for="shop-footer">
  <div class="orderly-shop-footer">
    <p class="orderly-shop-footer__about">Independent shop copy.</p>
  </div>
</template>
```

The programmatic `configureShopFooter()` API remains available when a shop needs to build footer data from JavaScript.

Templates:

- `footer`
- `address-line`
- `contact-item`
- `opening-hour`
- `information-link`
