React (Reference Theme)
Our reference themes are built with Vite + React:themes/defaultthemes/classic
Component Tree
The reference theme follows this hierarchy:| Layer | Purpose |
|---|---|
ThemeSettingsProvider | Fetches and merges theme settings, provides them via context |
ThemeStyleApplier | Reads resolved settings via useThemeSettings() and calls applyThemeSettingsToCss() |
InitialDataProvider | Passes injected initial data (window.__SHOPPEX_INITIAL__) down the tree |
ToastProvider | Notification system |
BrowserRouter | React Router for client-side navigation |
AppShell | Layout shell with Header, Routes, and Footer |
ThemeSettingsProvider wraps InitialDataProvider — settings are available everywhere, including in the data layer.Where SDK Init Happens
- HTML loads the SDK via CDN:
themes/default/index.html - The app bootstraps + calls
shoppex.init(...):themes/default/src/main.tsx
Resolve the store slug
From subdomain (
*.myshoppex.io), env var (VITE_SHOP_SLUG), or custom domain resolution via shoppex.resolveStoreByDomain().Check for initial data
Reads
window.__SHOPPEX_INITIAL__ (injected during build) and passes it to App as a prop.Where Data Loading Happens: useStore()
The useStore() hook is the single place where storefront data is resolved. It follows a two-tier strategy:
- Use InitialDataContext for first paint — if the page was pre-rendered (or the platform injected
window.__SHOPPEX_INITIAL__), the data is available immediately. - Revalidate via the SDK — even if initial data exists, your theme should still call
shoppex.getStorefront()aftershoppex.init()to protect against stale HTML (for example right after a migration/import). You can do this in the background and update state once the fresh response arrives.
Return Value
| Field | Type | Description |
|---|---|---|
store | Shop | null | Store metadata (name, logo, settings) |
products | Product[] | All products |
groups | ProductGroup[] | Product groups/collections |
isLoading | boolean | true while data is being fetched |
error | string | null | Error message if fetch failed |
The fetched storefront data is cached at module scope. Subsequent
useStore() calls in other components reuse the same data without re-fetching.Cart UI: useCartSync() and emitCartChanged()
useCartSync and emitCartChanged are theme-level patterns, not SDK features. The SDK manages cart data; these helpers keep the UI in sync.CartSnapshot Interface
useCartSync() returns a CartSnapshot — a reactive view of the current cart:
| Field | Type | Description |
|---|---|---|
items | CartItem[] | Current cart items |
itemCount | number | Number of distinct line items |
totalQuantity | number | Sum of all item quantities |
totalPrice | number | Computed total price |
totalPriceIsEstimate | boolean | Whether the price is an estimate (e.g. missing price data) |
emitCartChanged()
After any cart mutation, dispatch a custom event so all listeners re-read the cart:
useCartSync()
The hook listens for the custom event and storage events (for cross-tab sync) and returns the current cart snapshot:
getSnapshot() function calls shoppex.getCart() and shoppex.getCartStats() to build the snapshot.
Usage Pattern
addToCart, updateCartItem, removeFromCart, and clearCart. Always call emitCartChanged() after the SDK method.
Theme Settings: useThemeSettings() and applyThemeSettingsToCss()
ThemeSettingsProvider
The provider does three things at startup:Resolve defaults from theme.config.ts
Reads the settings schema and extracts all
default values into a ResolvedThemeSettings object.Fetch published overrides
If the SDK is initialized, calls
shoppex.fetchPublishedThemeSettings(shopSlug) to get merchant-configured values.useThemeSettings()
Returns the fully resolved settings from context (defaults merged with published overrides):
useThemeSettings() must be called inside ThemeSettingsProvider. It throws if used outside the provider tree.applyThemeSettingsToCss()
Maps resolved theme settings to CSS custom properties on document.documentElement. Called automatically by ThemeStyleApplier whenever settings change.
| Settings Path | CSS Variable | Description |
|---|---|---|
colors.background | --surface | Background color |
colors.surface | --card-bg | Card/surface color |
colors.primary | --brand-600 | Primary brand color |
colors.primaryDark | --brand-700 | Darker brand variant (fallback: colors.secondary, then darken primary 10%) |
colors.text | --text | Main text color |
colors.textMuted | --text-muted | Muted text color |
colors.textContrast | --text-contrast | Contrast text color |
colors.border | --border | Border color |
colors.muted | --muted | Muted background |
colors.accent | --accent | Accent color |
colors.hover | --hover | Hover state color |
colors.success | --success | Success color |
colors.error | --destructive | Error/destructive color |
typography.fontFamily | --font-family-body | Body font family |
typography.headingFont | --font-family-heading | Heading font (falls back to body) |
typography.fontSize | --font-size-base | Base font size in px |
effects.borderRadius | --radius | Global border radius in rem |
buttons.borderRadius | --button-radius | Button-specific radius in px |
inputs.height | --input-height | Input height in px |
inputs.borderRadius | --input-border-radius | Input-specific radius in px |
header.height | --header-height | Header height in px |
Color values are stored as space-separated RGB triplets (e.g.
124 58 237), not hex. Use rgb(var(--brand-600)) in CSS.SSR / SSG
If you need SSR/SSG, see:- Pre-Rendering (platform injection + theme-owned SSG)
- Build Pipeline (build steps)