npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@orderlyshop/web-components

v0.1.0-build.7068

Published

Headless native web components for Orderly storefronts.

Readme

@orderlyshop/web-components

Headless native web components for Orderly storefronts.

The package depends on @orderlyshop/core-client and wraps Core contracts such as SearchObject, SearchQuery, and DraftOrder in reusable browser components. Components render practical light-DOM defaults and expose typed properties, events, controllers, stores, and template overrides so host shops keep full control over CSS, layout, routing, and copy.

Agent Prompt Template

Copy this prompt into a coding agent when you want it to install the package and build a finished Orderly shop. Fill in known values first; if a value is filled in and is not ASK, the agent should use it without asking again.

Build a production-ready Orderly storefront with @orderlyshop/web-components.

Inputs:
- Project directory: ASK
- Shop name: ASK
- Orderly account id: ASK
- Visual style reference: ASK
- Deployment target: ASK
- Google Tag Manager container id: NONE
- Google Tag Manager data layer name: dataLayer
- Analytics consent categories: statistics
- Cookie policy URL: /information/cookies/

Use any provided input above without asking again. Ask only for values that are still `ASK`. Treat Google Tag Manager container id `NONE` or an empty value as an explicit choice to leave GTM/analytics disabled.

Workflow:
1. Install @orderlyshop/core-client and @orderlyshop/web-components if needed. Run npx orderly-init-shop for a new shop, pass --account-id when available, and preserve existing files unless I approve overwrite.
2. Read node_modules/@orderlyshop/web-components/AGENTS.md, DESIGN.md, README.md, docs/components/README.md, and docs/shop-best-practices.md before changing shop files. Treat docs/shop-best-practices.md as a completion checklist, not optional inspiration.
3. Scope the shop with the Orderly account id in src/shop-query.ts. When product sampling is possible, run npx orderly-init-navigation --suggest --account-id <account-id>, then build src/navigation.ts manually with stable unique slugs, at most two category levels, and plain SearchQueryInput objects.
4. Use package-owned components and configuration first: orderly-home-page, orderly-category-page, orderly-product-detail-page, orderly-checkout-page, configureShop(...), templates, generated Vite setup, generated category URLs, and package SSR helpers.
5. Use the visual style reference as design direction, not as copy material. Start src/style.css from DESIGN.md semantic tokens, then add scoped component selectors only where tokens are insufficient.
6. Do not ship the package default look and feel with minor color changes. Inspect the catalog, research the reference site and comparable modern shops, and create homepage/category/product context that is visually and editorially specific to this shop.
7. Aim for premium modern frontend craft: strong art direction, responsive composition, high-quality imagery, clear interaction states, appropriate motion, performance, and accessibility. Do not copy proprietary assets, text, logos, or brand marks unless I explicitly confirm they may be used.
8. Google Tag Manager is optional. Treat Google Tag Manager container id `NONE` or an empty value as an explicit choice to leave GTM/analytics disabled. If a container id is provided and is not `ASK`, wire it through `VITE_ORDERLY_GOOGLE_TAG_MANAGER_ID` and package config rather than raw GTM snippets. Use `configureShop({ consent, analytics })`, Cookie policy URL, Google Tag Manager data layer name, and Analytics consent categories from the inputs.
9. Configure the build/publish flow for the deployment target: npm run build for static hosting, npx orderly-publish-site --target ftp for FTP, package server helpers for Apache/PHP or Nginx/PHP, or explicit documented assumptions for other targets.
10. Run the nearest relevant build/test command. Start the dev server when a browser is available and visually inspect homepage, category, product, basket, and checkout on desktop and mobile before declaring the shop done. Report command and visual verification plus any missing backend, credential, research, analytics, browser, or deployment prerequisite.

Install

npm install @orderlyshop/core-client @orderlyshop/web-components

Create A Shop

For a new vanilla storefront, run the scaffold command from the shop project:

npx orderly-init-shop
npm install
npm run dev

Agents and developers should use npx orderly-init-shop before hand-writing shop files. It creates src/index.html, src/category.html, src/product.html, src/checkout.html, src/payment-success.html, src/payment-failure.html, src/includes/head.html, src/navigation.ts, src/shop-query.ts, src/templates/*.html, src/style.css, vite.config.mjs, and package scripts. The payment pages are physical callback URLs for payment providers and use the package orderly-payment-success-page and orderly-payment-failure-page components. Pass an account id when the shop-wide default query should be tenant-scoped from the start:

npx orderly-init-shop --account-id 00000000-0000-0000-0000-000000000000

For agent-assisted shop creation, ask for both the Orderly account id and a reference website URL for visual style before customizing the scaffold. Use the account id for src/shop-query.ts, use the reference website and catalog research to shape src/style.css, and follow docs/shop-best-practices.md as the completion checklist. The first pass should already feel visually distinct, editorially useful, and technically polished; it should not look like the default package scaffold with small token changes.

Re-running npx orderly-init-shop in an existing shop is non-destructive by default. Existing scaffold files are left unchanged with warnings, while missing new scaffold files are added. Use --force only when you explicitly want scaffold files overwritten.

The scaffold uses Vite for local development and static builds, but Vite is added to the generated shop's devDependencies. Dev mode SSR for product/category URLs is enabled by default, so View Source on local category/product URLs shows semantic server-rendered markup. Consumers should test locally from http://localhost:61677 or https://localhost:61677 because those are the expected local development origins for backend CORS. The generated Vite setup defaults to http://localhost:61677; if a local reverse proxy or TLS terminator is added, keep the same localhost:61677 origin over HTTPS. @orderlyshop/web-components does not depend on Vite at runtime.

Browser Standalone Bundle

The package build emits optimized ESM output and browser-ready standalone bundles with @orderlyshop/core-client, Connect, and protobuf runtime code included. JavaScript output is minified, and npm run build writes .br and .gz files next to every generated dist/**/*.js file for hosts that can serve precompressed assets. The standalone browser files are shipped in the npm package and are what the unpkg and jsdelivr package fields point at.

Use the configurable bundle when a vanilla HTML site needs to set shop options before registering elements:

<script src="/node_modules/@orderlyshop/web-components/dist/browser/orderly-web-components.global.js"></script>
<script>
  const { configureShop } = OrderlyWebComponents;

  configureShop({
    uiLanguage: "DA",
    defaultShop: {
      baseUrl: "https://service.orderly.shop",
      navigationDefinitions: [
        {
          label: "Sko",
          slug: "sko",
          query: { query: "sko sneakers" },
          children: [
            { label: "Dame sko", slug: "dame-sko", query: { query: "dame sko sneakers" } }
          ]
        }
      ]
    }
  });
</script>

Use the auto-register bundle for the built-in default shop or pages that configure everything declaratively:

<script src="/node_modules/@orderlyshop/web-components/dist/browser/orderly-web-components.define.global.js"></script>

The normal package entrypoints remain ESM for bundlers. The standalone files are classic browser scripts and expose window.OrderlyWebComponents.

configureShop() is the recommended one-call setup for HTML head scripts. Its parameter is strongly typed as ShopConfig, so VS Code/TypeScript can provide completions directly on the object literal. All fields are optional: uiLanguage, defaultShop, storefrontRouter, pageLayout, responsiveTemplates, shopFooter, storedImageUrls, consent, analytics, and components. It applies configuration first and then registers custom elements. The built-in SPA storefront router is enabled by default; pass storefrontRouter: { enabled: false } only when a shop needs traditional full-page navigation for every internal link. Pass components: false only when elements are registered elsewhere.

Built-in component copy is Danish by default (uiLanguage: "DA"). Set uiLanguage: "EN" to switch built-in labels, empty states, checkout copy, navigation controls, and page defaults to English. Shop-specific attributes, templates, and defaultShop label fields still take precedence over language defaults.

Cookie Consent

Use configureShop({ consent }) when the shop uses optional cookies, local storage identifiers, Google Tag Manager, analytics, campaign measurement, personalization, or similar tracking. The package-owned consent runtime gives visitors a first-layer banner with accept, reject, and customize actions, optional categories that are off by default, a persistent "Cookieindstillinger" button, versioned localStorage persistence, and first-party cookie cleanup when consent is rejected or withdrawn:

configureShop({
  consent: {
    enabled: true,
    version: "2026-04-30",
    policyHref: "/information/cookies/",
    managedCookies: [
      {
        name: "_ga",
        category: "statistics",
        provider: "Google Analytics",
        purpose: "Skelner mellem besøgende i statistikmåling.",
        duration: "Typisk op til 2 år"
      },
      {
        name: "_ga_*",
        category: "statistics",
        provider: "Google Analytics",
        purpose: "Bevarer sessionsstatus for statistikmåling.",
        duration: "Typisk op til 2 år"
      }
    ]
  }
});

Default categories are necessary, preferences, statistics, and marketing. necessary is always on; all other categories require an affirmative choice. Override categories and texts when a shop needs different purpose wording, and change version when purposes, vendors, or the cookie policy materially change so visitors are asked again. managedCookies accepts exact first-party cookie names or simple wildcard patterns such as _ga_*; include provider, purpose, and duration so the preferences layer can disclose the cookies it manages.

Programmatic helpers are exported for custom code: hasCookieConsent(category), currentCookieConsent(), saveCookieConsent(categories), openCookieConsentPreferences(), setOrderlyCookie(name, value, { category }), deleteOrderlyCookie(name), and clearCookiesForCategory(category). Use setOrderlyCookie for shop-owned optional cookies so they are only written when the category has consent.

The package can enforce consent for package-owned browser behavior, analytics hooks, and configured first-party cookies. The shop owner still has to keep the cookie policy/vendor list accurate, configure any server-side or third-party deletion outside the package, and ensure any custom scripts also check consent before reading or writing optional cookies.

Analytics And Google Tag Manager

Use configureShop({ analytics }) to install Google Tag Manager and publish GA4-compatible navigation and ecommerce events to dataLayer:

configureShop({
  consent: {
    enabled: true,
    policyHref: "/information/cookies/"
  },
  analytics: {
    googleTagManagerId: import.meta.env.VITE_ORDERLY_GOOGLE_TAG_MANAGER_ID,
    defaultCurrency: "DKK",
    affiliation: "My Shop"
  }
});

When googleTagManagerId is set, the package initializes dataLayer and injects the GTM script. The generated starter shop wires this to VITE_ORDERLY_GOOGLE_TAG_MANAGER_ID; omit the env var to leave tracking off. When package cookie consent is configured, analytics defaults to requiring the statistics category before GTM loads or package analytics events are pushed. Set analytics.consentCategories to another category, an array such as ["statistics", "marketing"], or false only when the shop handles consent outside the package.

With package cookie consent enabled, analytics also queues Google Consent Mode v2 default and update commands. The default command denies optional storage until the visitor has chosen; the update command is pushed immediately when the visitor saves or changes consent.

The analytics integration listens to package-owned DOM events and pushes these GA4 event names: page_view, view_item_list, select_item, view_item, add_to_cart, remove_from_cart, view_cart, begin_checkout, add_shipping_info, add_payment_info, and purchase. Ecommerce events clear the previous ecommerce object before pushing the new event, so GTM GA4 tags can read the current event payload cleanly. Purchase tracking fires from orderly-payment-success-page with transaction_id; configure the GA4 tag in GTM to use the data layer ecommerce object and avoid also firing a separate duplicate purchase tag on the same page.

For custom analytics, keep analytics.enabled: true without googleTagManagerId to use the same hooks with an existing GTM/dataLayer install, or pass onEvent to forward package events to another destination.

API Discovery

Developers and coding agents should start with these package-owned docs:

  • DESIGN.md defines the package-wide design tokens and the recommended theming workflow for consistent styling across all components.
  • docs/components/README.md lists every custom element, common attributes, typed properties, events, and template hooks.
  • docs/shop-best-practices.md is the practical checklist for turning the package scaffold into a branded, editorial storefront without replacing package-owned component behavior.
  • docs/components/product-grid.md documents search loading, sorting, paging, default query merging, and declarative query markup.
  • docs/components/product-rail.md documents homepage/category rails and their query configuration.
  • AGENTS.md gives coding agents the conventions for building shops with this package.
  • custom-elements.json is the Custom Elements Manifest for tools that understand web component metadata.
  • html-custom-data.json is VS Code compatible custom data for HTML autocomplete and hover docs.

These docs are written for GPT-5.5-class coding agents as well as humans: start with the outcome the shop needs, use the package-owned API surface, follow docs/shop-best-practices.md as the shop quality bar, and verify with both the nearest package/shop script and visual browser inspection before calling a shop finished.

Enable HTML editor hints in a consuming project by adding the package custom data file to .vscode/settings.json:

{
  "html.customData": [
    "./node_modules/@orderlyshop/web-components/html-custom-data.json"
  ]
}

The most important convention is that Core contracts use typed JavaScript properties, while simple search use cases can be declared in HTML. For example:

<orderly-product-rail
  title="Sko"
  cta-label="Se alle"
  cta-href="/categories/sko/"
  keywords="H&M"
  tags="børnetøj,bluser"
  order-by="CreatedTime desc">
</orderly-product-rail>

Default Shop

The package can render a working default storefront with the built-in backend, navigation, sorting, basket, product detail, and footer defaults:

import "@orderlyshop/web-components/define";
<orderly-category-page></orderly-category-page>

The default backend is https://service.orderly.shop. Category links use real /categories/<category-path>/ URLs by default so production builds can ship server-rendered category HTML. Use categoryUrlMode: "hash" only when a shop explicitly wants the older /category.html#id=<category> routing.

SPA Storefront Navigation

SPA-style storefront navigation is the default when a shop calls configureShop(). It is implemented as progressive enhancement, not as a replacement for normal URLs:

  • Links remain normal same-origin <a href> links, so Google, no-JS browsers, new tabs, copied links, and server-rendered entry pages still work.
  • Ordinary left-click navigation between known storefront routes is intercepted in the browser and rendered without a physical reload.
  • The router uses history.pushState, popstate, scroll restoration, canonical URLs, meta description updates, document title updates, and optional View Transitions.
  • A shared BasketController is kept alive across home, category, product, checkout, payment result, and configured static routes.
  • Unknown routes, external links, modified clicks, downloads, _blank links, and hash-only links fall back to the browser.

The default route coverage is:

  • home: defaultShop.homeHref, default /
  • categories: real category paths generated from navigationDefinitions, default /categories/<category-path>/
  • product details: either hash or path product URLs, controlled by defaultShop.productUrlMode
  • checkout: defaultShop.checkoutHref, default /checkout.html
  • payment result pages: /payment-success.html?orderid=... and /payment-failure.html?orderid=...
  • static pages: routes explicitly supplied in storefrontRouter.staticRoutes

The simplest setup needs no router block:

configureShop({
  defaultShop: {
    homeHref: "/",
    productHref: "/products/",
    productUrlMode: "path",
    productPathRoot: "/products/",
    checkoutHref: "/checkout.html",
    navigationDefinitions
  }
});

Use storefrontRouter only when the shop needs custom static pages, custom page factories, View Transition control, or an explicit opt-out:

configureShop({
  defaultShop: {
    homeHref: "/",
    productHref: "/products/",
    productUrlMode: "path",
    productPathRoot: "/products/",
    checkoutHref: "/checkout.html"
  },
  storefrontRouter: {
    product: {
      mode: "path",
      pathRoot: "/products/"
    },
    staticRoutes: [
      {
        path: "/forretningsbetingelser.html",
        title: "Forretningsbetingelser | Example Shop",
        description: "Køb, levering, betaling, retur og reklamation.",
        create: () => document.querySelector("#termsPage")!.cloneNode(true) as HTMLElement
      }
    ]
  }
});

Disable SPA navigation explicitly when a host application owns routing or when every internal link must perform a full document navigation:

configureShop({
  storefrontRouter: {
    enabled: false
  }
});

If a shop still uses hash-based product URLs, set productUrlMode: "hash" or storefrontRouter.product.mode: "hash". The package supports both #url=<encoded-share-url> and real product paths; path URLs are preferred for SEO, while hash URLs remain a fallback for shops that cannot add server-side product routing yet.

For SEO, keep physical pages or static generated pages available for every URL the router can render dynamically. The router improves navigation after the first load; it does not remove the need for server/static support for direct requests to category, product, checkout, and content URLs.

Customize the default once per shop from the HTML head:

import { create } from "@bufbuild/protobuf";
import { AccountIdSchema, SearchQuerySchema, UUIDSchema } from "@orderlyshop/core-client";
import { configureShop } from "@orderlyshop/web-components";
import { navigationDefinitions } from "./navigation";

const defaultQuery = create(SearchQuerySchema, {
  SourceAccountId: create(AccountIdSchema, {
    Id: create(UUIDSchema, { Value: "00000000-0000-0000-0000-000000000000" })
  }),
  HiddenQuery: "published"
});

configureShop({
  uiLanguage: "DA",
  defaultShop: {
    baseUrl: "https://service.orderly.shop",
    defaultQuery,
    brandLabel: "Example Shop",
    homeHref: "/",
    productHref: "/product.html",
    navigationDefinitions,
    sortOptions: [
      { label: "Newest", orderBy: ["CreatedTime desc"] },
      { label: "Price: low to high", orderBy: ["Price asc"] }
    ]
  },
  pageLayout: {
    logoSrc: "https://orderly.shop/home/App_Icon.svg",
    logoAlt: "Example Shop",
    logoHref: "/"
  },
  responsiveTemplates: {
    mobileMediaQuery: "(max-width: 860px)"
  }
});

Use English built-in copy by changing only the top-level language:

configureShop({ uiLanguage: "EN" });

defaultQuery is a full Core SearchQuery and is merged into every orderly-product-grid request, including grids inside orderly-category-page, orderly-collection-page, and orderly-product-rail. A tenant account id is just one possible field on that query. Advanced shops can also assign defaultQuery directly on a specific component from JavaScript. User-facing category and search text stays in SearchQuery.Query; any text from the default query is moved to HiddenQuery so it still narrows results without replacing the visible search term.

configureShop() validates navigationDefinitions. Every category needs an explicit globally unique slug using lowercase letters, numbers, and hyphens. If navigation is invalid, category pages render a visible configuration error that points at the broken item.

categoryPage.defaultQuery = defaultQuery;
productGrid.defaultQuery = defaultQuery;
productRail.defaultQuery = defaultQuery;

After that, category pages can stay declarative:

<orderly-category-page slug="shoes/women"></orderly-category-page>
<orderly-category-page slug="women"></orderly-category-page>
<orderly-category-page></orderly-category-page>

When no category attribute is set, orderly-category-page resolves the category from the current URL. It supports /category.html#id=shoes%2Fwomen and generated path pages such as /categories/shoes/women/. Hash ids are matched against both the generated category id and each navigation item's slug.

Example Shop

A complete vanilla HTML storefront example with strongly typed TypeScript navigation data is included in the npm package source at examples/shop.

After installing from the repository workspace, run it with:

npm run dev --workspace @orderlyshop/example-shop

Open the local shop from http://localhost:61677 during normal development, or https://localhost:61677 when a local TLS proxy is in front of the dev server. Consumers should keep that exact localhost origin because the backend CORS development policy expects it.

Inside an installed package, inspect the same reference implementation at:

node_modules/@orderlyshop/web-components/examples/shop

The example demonstrates how to customize the package default with Danish navigation, shop copy, declarative home/category/product/checkout pages, and the default SPA router. Its router config only adds static content routes and page factories; enabled: true is not required. It intentionally avoids local CSS and templates while the package default look and feel is developed. Shared component configuration lives in src/includes/head.html; the strongly typed navigation data lives in src/navigation.ts. It uses the package-level orderly-generate-category-pages and orderly-build-category-pages commands for simple real-URL category pages. It is included as readable source code and is not exported as part of the runtime API.

Navigation Setup

Installing @orderlyshop/web-components installs explicit shop and navigation helper tools. They do not run automatically during npm install.

For a new shop, start with the full scaffold:

npx orderly-init-shop

Create only a default strongly typed navigation.ts in the current project when the shop scaffold already exists:

npx orderly-init-navigation

Create the file somewhere else:

npx orderly-init-navigation --path src/navigation.ts

Agent workflow for a new shop:

  1. Ask for the Orderly account id and a reference website URL for the desired visual style, or a short style description when no reference site exists.
  2. Run the navigation helper when product sampling is possible:
npx orderly-init-navigation --suggest --account-id <account-id>
  1. Create navigation.ts manually from navigation-products.json. Export navigationDefinitions: NavigationDefinition[]; each item needs label, a globally unique slug, optional hero/content fields, optional children, and plain SearchQueryInput values such as { query: "sko sneakers", tags: ["sko"] }. Keep navigation to at most two category levels and avoid empty or tiny categories.
  2. Configure shop scope with a Core SearchQuery through configureShop({ defaultShop: { defaultQuery } }).
  3. Read and follow docs/shop-best-practices.md, translate the reference into DESIGN.md tokens plus scoped CSS, and add homepage/category content from catalog and research.
  4. Run the dev server when a browser is available and visually inspect homepage, category, product, basket, and checkout on desktop and mobile. Fix visible issues before reporting completion; visual QA is required for finished shop work.

The helper is intentionally an explicit tool for developers and coding agents. It is not a postinstall action. Agents should keep user-owned navigation edits, ask before overwriting files, and treat the product dump as source material for human-reviewable navigation.

Show tool help:

npx orderly-init-navigation --help

Optional flags:

npx orderly-init-navigation --suggest --account-id <account-id> --base-url https://service.orderly.shop --max-results 1000 --product-dump-path src/navigation-products.json

Use TypeScript output paths when the shop source is TypeScript:

npx orderly-init-navigation --suggest --account-id <account-id> --path src/navigation.ts --product-dump-path src/navigation-products.json

The product dump is sampled so large search indexes are not copied wholesale. It starts with every second product, then every third, fifth, eighth, and thirteenth product as the source result set grows. --max-results caps the number of product records written and defaults to 1000.

Navigation data has three layers:

  • NavigationDefinition is the authored data in navigation.ts. It is the only type most shops need to write. Every item needs an explicit stable slug; labels are display text and can change without changing URLs.
  • ResolvedNavigationItem is created by createCategoryNavigation() and consumed by default page components. It adds stable ids, hrefs, normalized Core SearchQuery values, and derived metadata.
  • NavigationMetadata contains those derived page values: slug, parent id, path segments, normalized query, search text, description, hero image, hero layout, hero cover count, and page root.

The older CategoryDefinition, CategoryNavigationItem, and CategoryMetadata names remain as compatibility aliases only.

Real Category URLs

Installing @orderlyshop/web-components also installs generic category page tools. Real path category URLs are the default because they are the best fit for SEO and static/server-rendered category pages.

Generate pages from the current shop's src/navigation.ts or src/navigation.js and category page template. If src/category.html exists, the generator uses it and writes generated source pages under src/categories; otherwise it falls back to category.html and categories:

npx orderly-generate-category-pages

Preview what would be generated:

npx orderly-generate-category-pages --dry-run

Remove generated pages:

npx orderly-generate-category-pages --clean

Build with generated category pages and then clean the generated source files. Scaffolded shops use this flow from npm run build by default:

npx orderly-build-category-pages

orderly-build-category-pages also hydrates the built dist/index.html and generated dist/categories/**/index.html pages with SEO-friendly fallback HTML. The hydration step embeds semantic product cards per category using SearchService.Search; it reads --base-url, VITE_ORDERLY_BASE_URL, or ORDERLY_BASE_URL, and otherwise uses the default https://service.orderly.shop backend. Live web components read the semantic fallback as initial visible state, hide the raw fallback markup, and then continue as normal client components. Product grids still run their first live SearchService.Search() after hydration when a client or backend URL is configured, so fallback products are replaced by current backend data and continuation-token paging starts from the live result set.

Hydrate an already-built site manually, for example in a nightly job:

npx orderly-hydrate-static-pages --base-url https://service.orderly.shop --products-per-category 12

Use --skip-products for metadata-only hydration, --strict-products when a nightly job should fail on backend search errors, and --no-hydrate on orderly-build-category-pages to keep the older unhydrated build behavior.

The category generator expects src/navigation.ts or src/navigation.js to export navigationDefinitions and the template page to contain <orderly-category-page></orderly-category-page>. TypeScript navigation files use the current shop project's typescript dependency for one-off transpilation. Useful options include --navigation, --template, --categories-dir, --site-title, --component-tag, and --export. The older --taxonomy flag and categoryDefinitions export are still accepted as compatibility aliases.

Static Vanilla Deployment

For static hosting, keep the generated shop as a normal Vite site and upload the built dist directory, not src or node_modules.

Recommended path-based setup:

  1. Keep productUrlMode: "path" or configure storefrontRouter.product.mode = "path" when the host can serve /products/.../ through the provided server helper or a static fallback.
  2. Use real category URLs by exporting navigationDefinitions and running npx orderly-build-category-pages from the shop project. This generates src/categories/**/index.html during build, hydrates dist/index.html plus dist/categories/**/index.html, and cleans generated source files afterwards.
  3. Upload every file in dist, including hashed assets under dist/assets and precompressed .br/.gz files when the host supports them.
  4. Configure the host to serve index.html, generated category index.html files, checkout/payment callback pages, and product fallback routing. Apache/PHP, Nginx/PHP-FPM, and Node helpers live in node_modules/@orderlyshop/web-components/server.
  5. Use hash product URLs only when the deployment target cannot route unknown product paths. Hash URLs still work with the SPA router, but path URLs are clearer for static/server-rendered SEO entry points.

For local dry runs use npm run build, then the host's static preview command or npm run preview from the generated shop. For one-off publish flows, use npx orderly-publish-site --target local --dir published-site or npx orderly-publish-site --target ftp.

Local SSR Dev Mode

Scaffolded shops use SSR in dev mode by default through the package Vite middleware:

import { orderlySsrDevServer } from "@orderlyshop/web-components/server/vite";

export default defineConfig({
  plugins: [htmlIncludes(), orderlySsrDevServer(), rootHtmlOutput()]
});

When you run npm run dev, product and category URLs such as http://localhost:61677/products/example.html/ and http://localhost:61677/categories/sko/ are served from the normal src/product.html and src/category.html templates with semantic SSR HTML injected before Vite transforms the page. Use browser View Source to inspect the server-rendered markup.

SSR responses include a small inline critical CSS block for the semantic fallback markup. Normal browser requests hide that fallback and route component light DOM, such as utility banners, until the web components hydrate, so users do not see an unstyled intermediate layout. Googlebot user agents receive the visible fallback layout instead. The HTML source is identical in both cases; only fallback visibility changes.

After the first SSR entry render, the default storefront router intercepts same-origin category, product, checkout, payment-result, and configured static-page links. Navigation then stays virtual with pushState and persistent app-shell state, while the anchors still keep real href values for SEO, no-JS clients, copied links, and direct entry URLs. Set storefrontRouter: { enabled: false } only if the host app deliberately wants physical navigation after every click.

Set ORDERLY_DEV_SSR=0 or run npm run dev -- --no-ssr to debug pure client-side rendering. Dev SSR uses VITE_ORDERLY_BASE_URL, ORDERLY_BASE_URL, or https://service.orderly.shop, defaults to grpc-web, and keeps a short per-URL in-memory cache. Override the cache with ORDERLY_DEV_SSR_CACHE_TTL_MS.

Publish Static Sites

Installing @orderlyshop/web-components also installs a publish command for the consuming shop project. Run it from the project directory. The command generates category pages, builds, hydrates the built homepage/category pages, and publishes the resulting dist folder as vanilla HTML/JS/CSS.

Publish the current project to a local folder:

npx orderly-publish-site --target local --dir published-site

Opt out of generated category pages when a project has its own server-side category routing:

npx orderly-publish-site --target local --dir published-site --no-category-pages

Publish the current project over FTP:

npx orderly-publish-site --target ftp

The FTP target prompts for server URL, remote folder, username, and password when values are missing. Values are saved in .orderly-publish.local.json in the current project directory and reused on the next publish. Keep that file out of git; it is written with owner-only file permissions where the OS supports it.

Re-enter saved FTP settings:

npx orderly-publish-site --target ftp --configure

Server Side Product And Category URLs

Production SSR for Apache/PHP is generated into the built site:

npm run build
npx orderly-generate-server-renderers --site-title "My Shop"

Scaffolded shops run that generator from npm run build by default. It writes dist/product.php, dist/category.php, dist/.htaccess, and dist/orderly-ssr-manifest.json. The PHP renderers call SearchService.Search over gRPC-web, inject semantic HTML into the built product.html or category.html, and preserve the normal web-component scripts and styles. The generated .htaccess sets long immutable cache headers for fingerprinted static assets and keeps HTML/PHP revalidatable; configure equivalent cache headers manually when deploying to hosts that ignore .htaccess.

Runtime options:

  • ORDERLY_BASE_URL overrides the backend URL used by PHP.
  • ORDERLY_SSR_TIMEOUT controls PHP backend timeout in seconds.
  • ORDERLY_SSR_HEADERS can add internal server-side request headers. Do not expose API keys or bearer tokens to browser JavaScript.
  • ORDERLY_SSR_MANIFEST can point PHP to a manifest outside the web root.

The semantic fallback uses real headings, links, images, schema.org Product/Offer metadata, and small data-orderly-* attributes only for behavior-critical values. Components hydrate from that HTML, then hide the fallback and continue as normal interactive web components. Product grids treat fallback products as temporary visual content and replace them with the first live search result when a backend client is available; without a live search target, the fallback remains visible. Generated SSR output keeps the same HTML for users and crawlers, hides fallback visibility for normal browser hydration, and serves a visible fallback layout to Googlebot. See server/README.md for details.

Define Elements

import { configureShop } from "@orderlyshop/web-components";

configureShop({
  responsiveTemplates: { mobileMediaQuery: "(max-width: 860px)" },
  storedImageUrls: {
    baseUrl: "https://static.example-cdn.com/",
    roles: {
      "product-tile": { width: 500, height: 500 },
      "product-detail": { width: 900, height: 900 },
      fullsize: false
    }
  }
});

Use a custom prefix when a shop already owns the default tag names:

configureShop({ components: { prefix: "shop" } });

The side-effect entrypoint registers the default orderly-* tags:

import "@orderlyshop/web-components/define";

The package installs baseline storefront CSS and structural lazy-load CSS when components are registered. The baseline CSS gives the default light-DOM components usable layout, spacing, responsive behavior, and product presentation without requiring a shop-owned stylesheet. Host shops can override it with normal CSS variables and orderly-* class selectors.

The lazy-load CSS can also be included as early as possible in the document head when pages contain slotted light-DOM content and need to avoid pre-hydration flashes:

import { ORDERLY_LAZY_LOAD_CSS } from "@orderlyshop/web-components";

const style = document.createElement("style");
style.textContent = ORDERLY_LAZY_LOAD_CSS;
document.head.append(style);

defineOrderlyWebComponents() installs the same lazy-load CSS at runtime as a fallback, but head inclusion is what prevents raw slotted links from flashing before JavaScript has loaded. The optional hydration fade is one-shot: it runs only for the first rendered component tree after a real page load, then the package removes data-orderly-hydration-fade so SPA navigation, browser back, product overlays, and other later renders do not fade or visually re-layout. The default component CSS is exported as ORDERLY_DEFAULT_CSS and installed automatically by defineOrderlyWebComponents().

Global Templates

Templates can live next to a specific element or in a shared page-level registry. Local templates still win, but components also look for document-level templates with data-orderly-for, data-orderly-component, or data-orderly-template-scope.

<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>
    <p data-orderly-bind="price"></p>
    <button type="button" data-orderly-action="add-to-basket">
      <span data-orderly-bind="basket-action-icon"></span>
    </button>
  </article>
</template>

This is useful for vanilla shops that include shared snippets such as body-start.html; all shop-specific templates can be included there instead of being repeated on every category page. A shop can keep each template in src/templates/*.html and use build-time comments such as <!-- orderly-include-template: ../templates/product-tile.html --> from body-start.html. data-orderly-for can target a full tag name such as orderly-product-tile, a prefix-independent component name such as product-tile, or *. Responsive global templates support the same data-orderly-viewport and data-orderly-media attributes as local templates.

JavaScript can also register templates:

import { registerGlobalTemplate } from "@orderlyshop/web-components";

registerGlobalTemplate({
  name: "product",
  for: "product-tile",
  template: `<article><h3 data-orderly-bind="title"></h3></article>`
});

Collection Page

<orderly-collection-page
  id="collection"
  title="Summer collection"
  description="Light layers and one-off pieces"
  hero-image="/images/summer.jpg">
  <template data-orderly-template="product">
    <article class="product-card">
      <orderly-product-tile></orderly-product-tile>
    </article>
  </template>
</orderly-collection-page>
import { create } from "@bufbuild/protobuf";
import { SearchQuerySchema } from "@orderlyshop/core-client";
import { createOrderlyWebClient } from "@orderlyshop/core-client/web";

const client = createOrderlyWebClient({ baseUrl: "https://api.orderly.example" });
const searchQuery = create(SearchQuerySchema, {
  Term: "summer"
});

collection.client = client;
collection.query = searchQuery;

Content and Brand Cookbook

The package defaults are a foundation, not the desired finished look for a branded shop. Start with the reference website and catalog, then make deliberate content and layout choices before replacing package templates.

For a fuller real-shop checklist, read Shop Best Practices before the first implementation pass.

  • Homepage: combine orderly-home-page or product rails with shop-owned editorial sections, seasonal/category rails, trust copy, store context, and strong imagery from the catalog or approved brand assets.
  • Category pages: use NavigationDefinition.description, heroImage, heroLayout: "covers" or heroLayout: "split", and heroCoverCount to create category headers with context and product-cover previews before adding custom HTML.
  • Product pages: use defaultShop.productDetail.trustNote, secondaryCta, and detail slots for store visits, secondhand curation notes, return policy, pickup information, or other shop-specific confidence builders.
  • Navigation: use slot="menu-before" and slot="menu-after" for store finder, donation, volunteer, campaign, or brand links; use persist-state for vertical desktop menus with nested categories.
  • Basket: use basket-mode="inline-desktop" on orderly-home-page, orderly-category-page, or orderly-product-detail-page when the desktop design needs a right rail that only appears after products are added. Use <orderly-basket-icon href="/checkout.html" placement="mobile-fixed" hide-when-empty> for a package-owned mobile cart/checkout button with count.
  • Checkout: keep platform copy out of the user-facing flow. Configure labels through defaultShop.checkoutLabels and basketLabels, then use scoped CSS and the checkout responsive override rules rather than replacing the checkout template.

Components

  • orderly-page-layout provides reusable page regions for header, header actions, responsive primary navigation placement, left/right sidebars, content, footer, and overlay content. It includes a default logo image using https://orderly.shop/home/App_Icon.svg; override it with logo-src, logo-alt, and logo-href. Use primary-nav-mobile-placement="header" for the common mobile header burger pattern without a duplicate mobile layout template.
  • orderly-home-page composes page layout, responsive primary navigation, one product rail per top-level category, basket icon, basket drawer, optional inline desktop basket rail, product detail dialog, and footer. Configure it with configureShop({ defaultShop }) plus attributes such as title, eyebrow, rail-cta-label, product-href, and basket-mode="inline-desktop". The product dialog is fullscreen on mobile, keeps a bounded image column on desktop through --orderly-product-dialog-image-size, and pushes a same-URL history entry when opened, so browser back closes the overlay before leaving the homepage. The expand link follows the configured product URL mode, and compact https://orderly.shop/... product URLs are preserved when it builds hash or path routes.
  • orderly-category-page composes page layout, navigation, search, product grid, product detail, basket icon, and basket around a navigation category. Without properties it uses the package default shop configuration. Override with configureShop({ defaultShop }) once per shop, set base-url or product-href in markup, or assign category, navigationItems, sortOptions, client, defaultQuery, and basketController from JavaScript for advanced cases. Configure defaultShop.navigationLayout or top-level navigationLayout to switch the package-owned category page from desktop side navigation to top horizontal category navigation while keeping the mobile burger in the header. Set basket-mode="inline-desktop" when the desktop category page should show a right basket rail only after the basket has lines. The product dialog is fullscreen on mobile, keeps a bounded image column on desktop through --orderly-product-dialog-image-size, and pushes a same-URL history entry when opened, so browser back closes the overlay before leaving the category. The expand link uses the configured product URL mode and still supports explicit per-page product-href overrides when a shop wants a different physical product page URL.
  • orderly-checkout-page composes page layout, responsive primary navigation, checkout form, basket order summary, footer, shared basket state, delivery loading, and order-created cleanup. Delivery methods render as radio cards after address entry, service points render as radio-card pickup choices when required, and delivery changes verify the draft so totals update. If a persisted draft already contains a complete address, delivery methods are restored on mount; if it also contains a delivery choice, the draft is verified so the basket summary can show backend shipping and total prices. Configure copy through configureShop({ defaultShop: { checkoutPageTitle, checkoutPageDescription, checkoutOrderTitle, checkoutTermsHref, checkoutLabels, basketLabels } }), or assign client, basketController, navigationItems, checkoutLabels, and basketLabels from JavaScript.
  • orderly-product-detail-page composes page layout, navigation, product detail, basket drawer, optional inline desktop basket rail, and footer for a product route. Set share-url, the shareUrl property, or pass #url=<encoded-share-url> in the page URL; compact Orderly slugs are restored to https://orderly.shop/... before it calls SearchService.Search with SearchQuery.ShareUrl and renders the resolved SearchObject. It forwards details-before, purchase-note, secondary-cta, and details-after slots to the nested product page. Set basket-mode="inline-desktop" when a product page should keep the basket visible as a desktop right rail after add-to-basket. Shops can tune outer gutters with --orderly-product-detail-page-padding, --orderly-product-detail-page-mobile-padding, --orderly-product-detail-page-inline-padding, and --orderly-product-detail-page-mobile-inline-padding.
  • orderly-stored-image accepts image: StoredImage, resolves image URLs from the configured prefix, and applies RotationDeg, crop, fit, and role-based CDN sizing in the browser. Default roles include product-tile 500x500, product-detail 900x900, 180x180 thumbnails, and fullsize without size params.
  • orderly-credit accepts credit: Credit or declarative amount and currency attributes, then renders stylable money markup. DKK values are formatted as kr. <pris>, for example kr. 100,00.
  • orderly-product-tile accepts product: SearchObject and emits orderly-product-selected, orderly-add-to-basket, and orderly-remove-from-basket. Templates can bind the product share URL with data-orderly-bind="share-url"; anchors without an existing href keep the raw share URL, while anchors that already point at a product page are rewritten to the configured physical product route using hash or path mode. Default tiles, generated starter-shop tile templates, and statically hydrated category tiles include schema.org/Product, nested schema.org/Brand, and schema.org/Offer microdata for Google product snippets: name, image, URL, description, SKU, brand, price, currency, and in-stock availability are emitted when those fields are present. Custom tile templates should preserve the data-orderly-bind="schema-*" placeholders plus the data-orderly-schema-brand and data-orderly-schema-offer wrappers. When a BasketController is assigned through basketController, the default action button toggles between add and remove, using an icon-only button next to the price. Keyboard focus is indicated on the product title instead of a frame around the whole tile; custom tile templates should keep a title element with data-orderly-bind="title" or .orderly-product-tile__title.
  • orderly-product-page accepts product: SearchObject, renders all product image thumbnails, switches the selected image on thumbnail click, and exposes product details plus add-to-basket behavior. It emits orderly-product-viewed once per displayed product, so analytics integrations can track view_item without replacing the template. Product-page templates bind title, description, brand, size, color, condition, price, image, addLabel, and closeLabel; they also support basket-action-label, basket-action-icon, and basket-action-state so product-page add-to-basket markup can share the same binding names as product tiles. The default product template has extension slots details-before, purchase-note, secondary-cta, and details-after; use these for shop-specific notices or links without replacing the whole product template. For simple default content, configure defaultShop.productDetail.trustNote and defaultShop.productDetail.secondaryCta. The fullscreen image viewer is package-owned, so custom product templates only need inline image markup plus optional thumbnail markup; the overlay, click/wheel zoom behavior, labels, focus handling, square image viewport, and overlay thumbnails are injected by the component itself unless image-viewer="false" is set. When opened inside an existing product dialog, the viewer is portaled into that open <dialog> so it stays above the product overlay; otherwise it is portaled to document.body. Main image frame styling lives on the default wrapper, not the inner orderly-stored-image, and shops can tune spacing/image framing with --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, and --orderly-product-page-thumbnail-border.
  • orderly-search-box binds to a target orderly-product-grid. Use mode="textbox" for an inline search input or mode="icon" for a header icon that opens a full-page search overlay. Icon mode searches automatically with a 500ms debounce and renders matching product tiles below the search field. Default home and category pages use icon mode next to the basket icon.
  • orderly-product-grid accepts query: SearchQuery, merges the configured shop query scope, calls SearchService.Search, renders its own sort control from sortOptions, supports opaque ContinuationToken paging, manual paging, dynamic/infinite scroll, and default loading placeholders while the first page is pending.
  • orderly-product-rail accepts query: SearchQuery, title, CTA label, and CTA href, then renders the search as a horizontal scroll list by composing orderly-product-grid. It is useful for homepages and editorial rows where several category searches should be stacked vertically.
  • orderly-collection-page accepts query: SearchQuery, title, description, hero image, hero-layout, and hero-cover-count, then delegates fetching to orderly-product-grid. Use hero-layout="covers" to show product-cover previews from the loaded category products without custom category header HTML.
  • orderly-shop-footer renders configurable logo, about text, address, contact information, opening hours, and information links.
  • orderly-basket-icon and orderly-basket share a BasketController backed by DraftOrder persistence. Basket state events such as orderly-basket-open, orderly-basket-change, and orderly-basket-verified use the current DraftOrder directly as event.detail, so consumers can derive counts and totals from Core contracts instead of an internal basket data shape. orderly-basket-icon can also render a checkout/cart link with href, placement="mobile-fixed", and hide-when-empty for a package-owned mobile button with count. orderly-basket-checkout includes { draft, count, href } when the checkout action is triggered. orderly-basket renders an item overview, verifies non-empty persisted drafts on load plus later basket mutations through OrderService.VerifyDraft when a client or base-url is configured, includes a configurable checkout link through checkout-href and checkout-label, and only exposes quantity selection for lines where MaxQuantity > 1. Summary rows now render only backend-provided DraftOrder values, never local subtotal/total math, and the component renders both DraftOrder.Errors and DraftOrderLine.Errors. Custom basket templates can project order-level errors through data-orderly-slot="errors".
  • orderly-checkout persists checkout contact and address fields directly on DraftOrder.Transport, defaults the phone country picker to +45, validates email and phone values with built-in component logic, looks up Danish city names from the entered postal code through Dataforsyningen's https://api.dataforsyningen.dk/postnumre/{postnr} endpoint, loads delivery methods and service points, verifies the draft, and calls OrderService.Create. On mount it restores delivery methods for a persisted complete address and verifies a persisted delivery/service-point choice once, so shared basket summaries receive backend shipping and total prices without waiting for another address edit.
  • orderly-navigation uses NavigationController and can be subclassed with site-specific NavigationItem[]. It is sticky by default, including when placed in left, right, or primary-nav; set sticky="false" to opt out. Use layout="vertical" for disclosure side navigation, layout="horizontal" for the tiered desktop bar, or layout="burgermenu" for an icon trigger that expands into the same nested disclosure menu inside a floating panel. Use layout-desktop="horizontal" layout-mobile="burgermenu" with orderly-page-layout primary-nav-mobile-placement="header" for desktop horizontal nav plus inline mobile burger. Add persist-state state-key="main-menu" to remember expanded category branches across navigation, and use slot="menu-before"/slot="menu-after" for shop-owned links such as store finder, campaigns, or volunteer pages.
  • orderly-search-box, orderly-sort-select, orderly-filter-panel, and orderly-load-more bind to orderly-product-grid.

Navigation Patterns

There are two different mobile navigation patterns:

  • layout="horizontal" means the navigation owns its responsive fallback where it is placed. It renders the desktop horizontal category bar and automatically falls back to drawer-style mobile navigation on narrow viewports, but it does not move the trigger into the page header action row.
  • layout-desktop="horizontal" layout-mobile="burgermenu" plus primary-nav-mobile-placement="header" means desktop horizontal navigation and an inline mobile burger in the page header next to search, basket, or other icon buttons. This is the recommended setup for the common storefront header pattern.
  • Custom data-orderly-viewport="mobile" page-layout templates are only needed when a shop replaces the whole page layout and wants manual control over where data-orderly-slot="primary-nav" is rendered.

Package-owned home, category, product, checkout, and payment pages read the same pattern from configuration:

configureShop({
  navigationLayout: {
    primaryNavDesktopPlacement: "primary-nav",
    primaryNavMobilePlacement: "header",
    layoutDesktop: "horizontal",
    layoutMobile: "burgermenu"
  }
});
<orderly-page-layout primary-nav-mobile-placement="header">
  <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>

Basket Layout And Count

Use the package basket primitives before writing custom state glue:

<orderly-category-page basket-mode="inline-desktop"></orderly-category-page>
<orderly-basket-icon href="/checkout.html" placement="mobile-fixed" hide-when-empty label="Kurv"></orderly-basket-icon>

basket-mode="inline-desktop" is supported by orderly-home-page, orderly-category-page, and orderly-product-detail-page. It keeps the existing drawer behavior on mobile, but on desktop it shows a right rail only when BasketController.count > 0.

Basket count contract:

  • BasketController.count is derived from DraftOrder.OrderLines[].Quantity.
  • orderly-basket-icon, orderly-basket, product tiles/grids/rails, product pages, and checkout should share the same BasketController instance.
  • orderly-basket-change and orderly-basket-verified expose the current DraftOrder as event.detail; custom UI can derive counts from that draft without copying package state.
  • Verified and persisted drafts still use the same DraftOrder; backend-provided prices and errors are rendered by orderly-basket.

Checkout Responsive Overrides

When customizing checkout layout, do not override the package mobile collapse accidentally:

  • Keep desktop-only grid changes inside @media (min-width: 768px) or the same breakpoint configured through configureShop({ responsiveTemplates }).
  • Avoid global grid-template-columns overrides on .orderly-checkout-page__grid, .orderly-checkout__field-grid, .orderly-checkout__delivery-grid, and .orderly-checkout__option-grid--delivery without a matching mobile reset.
  • Prefer CSS variables and scoped spacing/color rules on .orderly-checkout-page, .orderly-checkout-page__card, .orderly-checkout, and .orderly-checkout__option.
  • Keep .orderly-checkout-page__card--summary as a normal layout container; the package moves submit/terms controls there for the responsive checkout summary.

Rendering And Styling

The package ships baseline default CSS and uses light DOM. Default markup uses stable orderly-* class names and CSS variables, so host CSS can style or replace the default look directly.

For consistent theming across the whole package, start with the semantic design contract in DESIGN.md. In practice that means overriding --orderly-color-primary, --orderly-color-primary-soft, --orderly-color-primary-contrast, the --orderly-action-primary-* and --orderly-action-secondary-* tokens, plus shared tokens such as --orderly-link-accent-color, --orderly-selection-*, and --orderly-badge-*. --orderly-color-accent is kept as a compatibility alias, but new themes should treat --orderly-color-primary as the source of truth.

The package declares its fallback tokens with :where(:root), which has zero selector specificity. If your shop sets tokens on :root, those values win even when defineOrderlyWebComponents() injects the package CSS after your stylesheet. You should not need to duplicate token overrides on body unless you intentionally want a scoped override.

Example:

:root {
  --orderly-color-primary: #1f7a43;
  --orderly-color-primary-soft: #e7f4eb;
  --orderly-color-primary-contrast: #ffffff;
  --orderly-link-accent-color: var(--orderly-color-primary);
  --orderly-selection-border: var(--orderly-color-primary);
  --orderly-selection-background: var(--orderly-color-primary-soft);
  --orderly-badge-background: var(--orderly-color-primary);
  --orderly-badge-color: var(--orderly-color-primary-contrast);
  --orderly-action-primary-background: var(--orderly-color-primary);
  --orderly-action-primary-border: var(--orderly-color-primary);
  --orderly-action-primary-color: var(--orderly-color-primary-contrast);
  --orderly-action-secondary-border: var(--orderly-color-primary);
  --orderly-action-secondary-color: var(--orderly-color-primary);
}

Rendering can be replaced with templates:

<orderly-page-layout>
  <template data-orderly-template="layout">
    <section class="shop-layout">
      <header data-orderly-slot="header"></header>
      <aside data-orderly-slot="left"></aside>
      <main data-orderly-slot="content"></main>
      <footer data-orderly-slot="footer"></footer>
    </section>
  </template>

  <a slot="header" href="/">Shop</a>
  <shop-navigation slot="left"></shop-navigation>
  <orderly-collection-page slot="content"></orderly-collection-page>
</orderly-page-layout>

<orderly-page-layout
  logo-src="/brand/logo.svg"
  logo-alt="Example shop"
  logo-href="/">
  <a slot="header" href="/">Example shop</a>
  <orderly-shop-footer slot="footer"></orderly-shop-footer>
</orderly-page-layout>

<orderly-product-grid id="grid" paging="dynamic">
  <template data-orderly-template="layout">
    <section class="catalog">
      <div data-orderly-slot="sort"></div>
      <p data-orderly-bind="status"></p>
      <ol data-orderly-slot="items"></ol>
      <span data-orderly-sentinel></span>
    </section>
  </template>
  <template data-orderly-template="empty">
    <p class="empty-state">No matches.</p>
  </template>
  <template data-orderly-template="product">
    <li class="catalog-item">
      <orderly-product-tile></orderly-product-tile>
    </li>
  </template>
</orderly-product-grid>

For dynamic paging, orderly-product-grid keeps the returned PageResult.Continuation as an opaque ContinuationToken and sends it as SearchQuery.Continuation when the sentinel enters the viewport. Dynamic and infinite paging do not render a load-more button. Use paging="button" only when a visible load-more button is desired, paging="manual" when another control calls loadNextPage(), and paging="dynamic" or paging="infinite" for automatic loading.

Every component has a default light-DOM implementation and matching template hooks. Use data-orderly-bind for values, data-orderly-action for behavior, and data-orderly-slot for repeated or projected content. Product tile templates can bind basket-action-icon, basket-action-label, and basket-action-state and use data-orderly-action="add-to-basket" for the add/remove toggle. Product page templates keep the existing addLabel binding and also support basket-action-label, basket-action-icon, and basket-action-state for the same add-to-basket concept; use data-orderly-action="add-to-basket" for the action. Product page galleries use data-orderly-slot="thumbnails" and data-orderly-action="select-image" for custom thumbnail markup, while the fullscreen viewer overlay stays package-owned; shops can optionally mark a custom trigger with data-orderly-image-viewer-trigger, otherwise the inline data-orderly-bind="image" element becomes the trigger automatically. The viewer overlay may be moved outside the product-page host while open, either to document.body or to the nearest open <dialog>, so custom CSS or tests should target [data-orderly-image-overlay] instead of assuming the overlay remains inside orderly-product-page. Basket templates can add data-orderly-slot="errors" to place order-level DraftOrder.Errors exactly where a shop wants them. Product rail templates can replace the outer layout, inner grid, product item, loading state, and empty state. The main template names are layout, grid, footer, product, thumbnail, image, basket, basket-icon, line, checkout, navigation, item, search-box, sort, sort-select, filter-panel, load-more, address-line, contact-item, opening-hour, and information-link.

Use product page detail slots for small shop-specific content without replacing the package product template:

<orderly-product-detail-page>
  <section slot="purchase-note">
    <strong>Kurateret secondhand</strong>
    <p>Alle varer er udvalgt og pakket i butikken.</p>
  </section>
  <a slot="secondary-cta" href="/butik/">Besøg butikken på Nørrebrogade</a>
</orderly-product-detail-page>

Product sorting belongs to orderly-product-grid: assign grid.sortOptions = [{ label, orderBy }] or pass sort options through orderly-collection-page.sortOptions. The default grid layout renders the sort select above the products. Custom grid layouts should include data-orderly-slot="sort" where the control should appear, and custom sort templates can use data-orderly-template="sort" with a <select data-orderly-action="sort">.

Simple product queries can be declared in markup on orderly-product-grid, orderly-product-rail, and `orderly-collection-