Sign in →
Embed Widgets1 min read

AforoPricingCard

Anonymous pricing display with three layouts. Auto-fetches your published offerings and renders them with your brand kit. No auth required — drop on any marketing page.

Updated 2026-06-15Suggest edits
Docs Embed Widgets PricingCard
Live preview — synthetic data
AforoPricingCard
Anonymous pricing display — reads headless config, no session required.
Loads the widget from https://embed-demo.aforo.ai with synthetic data.

What it renders#

AforoPricingCard renders your tenant's published offerings as a pricing card grid. The widget calls Aforo's headless config endpoint on mount, filters to status === 'PUBLISHED', and renders one card per offering with your brand kit colors.

Three layouts to pick from:

  • horizontal (default) — auto-fit grid, cards 260px wide minimum. Good for marketing pages with 3-4 plans.
  • vertical — single-column stack. Good for narrow viewports or sidebars.
  • table — semantic <table> with one column per plan and one row per feature. Good for plan comparison pages with 5+ plans.
From Operator Portal → Settings.
From Operator Portal → Embed Studio → Keys.
Script tag
<div data-aforo-widget="pricing-card"
     data-aforo-tenant-slug="your-tenant"
     data-aforo-embed-key="ek_live_replace_me"
     data-aforo-layout="horizontal"></div>

<script src="https://cdn.aforo.ai/embed/loader.js" async></script>
NPM / React
import { AforoPricingCard } from '@aforoai/storefront-widgets';

export function Pricing() {
  return (
    <AforoPricingCard
      tenantSlug="your-tenant"
      embedKey="ek_live_replace_me"
  layout="horizontal"
    />
  );
}
Preview with my account
Fill in Tenant slug above to enable this preview.

Mount examples#

pricing.html
<script
  src="https://embed.aforo.ai/v1/loader.js"
  integrity="sha384-…"
  crossorigin="anonymous"
  async
></script>

<div
  data-aforo-widget="pricing-card"
  data-tenant-slug="acme"
  data-embed-key="embk_live_…"
  data-layout="horizontal"
  data-featured-offering-id="off_pro"
></div>

Props reference#

PropTypeDefaultDescription
tenantSlugstring(required)Your tenant slug (e.g. "acme")
embedKeystring(required)Embed key minted in Embed Studio
layout"horizontal" | "vertical" | "table""horizontal"Card layout
featuredOfferingIdstring?Highlight one plan with a "Most popular" badge
maxPlansnumber?Cap the number of cards rendered
ctaUrlstring?Default URL for every CTA button
ctaUrlByOfferingRecord<string, string>?Per-offering URL overrides (object keyed by offering ID)
theme"auto" | "light" | "dark""auto"Color scheme
themeOverridesPartial<ThemeTokens>?Per-widget theme tuning
localestring?navigator.languageBCP-47 locale for Intl currency formatting
onCtaClick(payload) => voidReact callback for CTA clicks (in addition to postMessage)
parentOriginstring?window.location.originOverride the postMessage targetOrigin

Events#

PricingCard emits the standard aforo.pricing-card.ready and aforo.pricing-card.error lifecycle events, plus one widget-specific event:

cta-handler.ts
window.aforoEmbed?.on(
  'aforo.pricing-card.cta_clicked',
  (payload) => {
    // payload = {
    //   offeringId: string,
    //   planName: string,
    //   priceCents: number,
    //   currency: string,
    //   billingCycle: 'monthly' | 'yearly',
    //   ratePlanId?: string,
    // }
    track('pricing_card_clicked', payload);
  },
);

See the for the full envelope shape and origin allowlist rules.

Common patterns#

Redirect to your own checkout

If you already have your own checkout flow, pass ctaUrlByOffering with one URL per offering:

page.tsx
<AforoPricingCard
  tenantSlug="acme"
  embedKey="embk_live_…"
  ctaUrlByOffering={{
    off_starter: '/checkout?plan=starter',
    off_pro: '/checkout?plan=pro',
    off_enterprise: 'mailto:sales@acme.com',
  }}
/>

React onClick callback instead of redirect

For SPA navigation, omit URLs and use onCtaClick:

page.tsx
import { useRouter } from 'next/navigation';

const router = useRouter();

<AforoPricingCard
  tenantSlug="acme"
  embedKey="embk_live_…"
  onCtaClick={({ offeringId }) => {
    router.push(`/subscribe/${offeringId}`);
  }}
/>

Highlight a featured plan

page.tsx
<AforoPricingCard
  tenantSlug="acme"
  embedKey="embk_live_…"
  featuredOfferingId="off_pro"
/>

The featured card gets a 2px border in your primary color plus a "Most popular" badge with both visible text and an aria-label alternative (color is supplementary, not the only signal).

Limitations#

INFO
Custom CTA text per offering (e.g. "Start Free Trial" vs "Contact Sales") is a Phase 1 feature. v0.1.x always renders "Subscribe" or your configured default text. Vote up FR-WIDGET-PC-7 in the roadmap if you need this soon.
  • Anonymous mode only. PricingCard never requires a bridge token — it's designed for marketing pages where the visitor has no identity yet.
  • Published offerings only. Draft / archived offerings are filtered server-side. Publish from Storefront → Offerings before they appear.
  • No usage simulator. PricingCard renders the displayed price as configured on the rate plan. Per-plan usage estimators are territory (Phase 1).