NUMU Docs
Contact
APIThemes
Partner Apps
APIThemes
Partner Apps
  1. Theme SDK
  • πŸ—‚οΈ Themes overview
  • πŸš€ Start here
  • Glossary
  • Theme engine
    • Architecture
    • BYOT contract
    • Customizer
    • Federation runtime
    • Page data contract
    • Theme manifest
  • Theme SDK
    • Components
    • Federation helpers
    • Hooks
    • SDK overview
    • Type definitions
  • CLI & Vite plugin
    • CLI commands
    • CLI overview
    • Lint rules
    • Section library
    • Vite plugin
  • Storefront host
    • API proxies
    • Built-in fallbacks
    • BYOT fork
    • Routing
    • Storefront overview
  1. Theme SDK

Components

Every public React component in @numueg/theme-sdk.

Provider + boundaries#

<NuMuProvider>#

Root provider. Wraps your theme's component tree. Must be the outermost wrapper inside mount().
import { NuMuProvider } from "@numueg/theme-sdk";

export function mount(ctx) {
  root.render(
    <NuMuProvider {...ctx}>
      <App />
    </NuMuProvider>
  );
}
Sets up: ShopContext, CustomerContext, ThemeSettingsContext, LocalizationContext, PageContext, CartContext. Reads the customer_access_token cookie on mount to hydrate the customer.
Props (all from MountContext): store, themeSettings, page, locale, direction, currency.

<ProductProvider> / <CollectionProvider>#

Overrides for nested trees. Useful when you render multiple products on one page (collection grid, recommendations slider):
<ProductProvider product={p}>
  <ProductCard />
</ProductProvider>
Inside the provider, useProduct() returns p.

<Section> / <Block>#

Section + block boundaries with built-in <ErrorBoundary>. Resolve the current section/block from useSection().
<Section>
  {(section) => (
    <div className="hero">
      <h1>{section.settings.headline}</h1>
      {section.block_order?.map(id => <Block key={id} blockId={id} />)}
    </div>
  )}
</Section>
A section's render function receives the resolved instance; <Block blockId> looks up the block by id in the section's blocks map and renders the registered block component.
If a block component errors, the boundary catches it and renders a fallback. This is critical β€” a broken block must not take down the whole section.

UI primitives#

<Image>#

<Image
  src="/uploads/abc.jpg"
  alt="Product"
  width={600}
  height={600}
  sizes="(min-width: 768px) 50vw, 100vw"
  loading="lazy"
/>
Generates srcset via the platform's image-transform service (Phase 4.2). Falls back to the raw URL when the service is unavailable.

<Money>#

<Money cents={12999} />          // β†’ "129.99 EGP" (RTL-aware in Arabic locale)
<Money cents={12999} currency="USD" />
Reads locale + direction from context. Respects useCurrency() for multi-currency presentment.

<Link>#

<Link to="/products/abc">View product</Link>
<Link to="/cart" external={false}>Cart</Link>
Internal-first. Resolves the URL via useShop().formatUrl() so subdomain awareness Just Works. External links use <a> + target="_blank" + rel="noopener".

<RichText>#

DOMPurify-sanitized HTML renderer.
<RichText html={page.body_html} />
Use this instead of dangerouslySetInnerHTML β€” themes that use raw dangerouslySetInnerHTML will trip the forbidden-script-tag lint rule (and potentially get rejected at admin review).

<Form>#

Server-action-style form with progressive enhancement.
<Form
  action="/api/customer/login"
  method="POST"
  onSuccess={(data) => router.push("/account")}
  onError={(err) => setError(err.message)}
>
  <input name="email" type="email" required />
  <input name="password" type="password" required />
  <button type="submit">Sign in</button>
</Form>
Handles CSRF + idempotency-key + serializes form data β†’ JSON automatically. The form's submit button gets aria-busy while in flight.

E-commerce components#

<AddToCartButton>#

Variant-aware, with built-in UX state machine.
<AddToCartButton
  product={product}
  variant={selectedVariant}      // optional β€” defaults to default variant
  quantity={qty}
  label="Add to bag"
  loadingLabel="Adding…"
  soldOutLabel="Sold out"
  errorLabel="Try again"
  onAdded={(p, v) => track("add_to_cart", { product_id: p.id })}
  className="theme-btn-primary"
/>
States: idle β†’ adding β†’ idle (or β†’ error for 2s, then idle). When sold-out, renders disabled with soldOutLabel. Wraps useCart().addItem() so themes don't need to reimplement the loading + error UX.

<ProductCard> / <CollectionCard>#

Opinionated default cards. Themes that don't want the defaults can ignore them and render their own. Themes that want a quick start can use them straight:
<ProductCard product={p} />

<CollectionCard collection={c} />
Each respects the current locale + currency + RTL.

<CurrencySwitcher> / <LocaleSwitcher>#

Header/footer toggles.
<LocaleSwitcher />                 // dropdown of supported locales
<CurrencySwitcher />               // dropdown of presentment currencies
Both update cookies + reload the page. Hidden when only one option is available.

<CookieConsent>#

GDPR banner (Phase 5.6).
<CookieConsent
  text="We use cookies to improve your experience."
  acceptLabel="Accept"
  denyLabel="Deny"
  storageKey="numu_cookie_consent"
/>
Stores the preference in localStorage. Until the banner is dismissed, analytics dispatch is paused.

Internals (rarely needed in themes)#

ExportPurpose
ShopContext, CartContext, CustomerContext, etc.Raw context objects β€” escape hatches for advanced patterns
registerSdkSingleton / getSdkSingletonFederation runtime singletons
registerReactSingleton / getReactSingletonReact identity registration
isSdkAvailable()Check the SDK has loaded (for error boundaries)
resolveThemeSettings(raw)Normalize legacy customizer field shapes
sanitizeHtml(html)Same DOMPurify config <RichText> uses
assetUrl(path)Resolve a theme asset URL
Avoid these unless you have a specific need β€” they exist for the SDK's own infrastructure and may change without semver bumps.

Patterns#

Loading states#

The Optional hook pattern returns null on miss. Cards that might render outside a context should branch:
function MaybeProductCard() {
  const product = useProductOptional();
  if (!product) return null;
  return <ProductCard product={product} />;
}

Async data#

useProducts, useCollections, useOrders, etc. expose { items, loading, error }. Render a skeleton during loading, an error UI on error, and the data on success:
const { items, loading, error } = useProducts({ limit: 8 });
if (loading) return <ProductGridSkeleton count={8} />;
if (error)   return <div>Couldn&apos;t load products.</div>;
return <div className="grid">{items.map(p => <ProductCard key={p.id} product={p} />)}</div>;

Error boundaries#

<Section> and <Block> ship with their own boundaries. For top-level page errors, wrap your route component in a custom boundary:
import { ErrorBoundary } from "react-error-boundary";

export default function ProductPage() {
  return (
    <ErrorBoundary fallback={<div>Something went wrong.</div>}>
      <Product />
    </ErrorBoundary>
  );
}
The SDK doesn't export an <ErrorBoundary> β€” use the community react-error-boundary package (it's small enough that themes bundle it).
Modified atΒ 2026-09-19 15:52:25
Previous
Theme manifest
Next
Federation helpers
Built with