The three-layer cascade#
Every widget reads its visual tokens through a three-layer cascade. Each later layer overrides the previous one, so you can configure broad defaults at the brand-kit level and fine-tune individual widgets in code.
LAYER 1
Workspace brand kit
Configured once in Storefront → Customize. Logo, primary color, text color, font. Aforo fetches this when the widget mounts.
LAYER 2
CSS variables
Override at the page level via custom properties on :root or a containing element. Cascades naturally.
LAYER 3
Per-widget overrides
The themeOverrides prop / data-theme-overrides attribute. Highest specificity. Use for one-off tuning.
If the workspace brand kit isn't configured (new workspaces), the SDK falls back to a neutral default palette. Your widgets won't look broken; they'll just look generic until you fill in the brand kit.
CSS variables reference#
Set these custom properties on :root or any ancestor of your widget placeholder to override the brand kit:
:root {
/* Brand */
--aforo-color-primary: #1A73E8;
--aforo-color-on-primary: #FFFFFF;
/* Surfaces */
--aforo-color-bg: #FFFFFF;
--aforo-color-surface: #F8FAFB;
--aforo-color-card: #FFFFFF;
/* Text */
--aforo-color-text: #0B1526;
--aforo-color-text-muted: #6A83A3;
/* Borders + focus */
--aforo-color-border: #DDE0E5;
--aforo-color-focus-ring: rgba(26, 115, 232, 0.45);
/* Typography */
--aforo-font-family: 'Inter', -apple-system, sans-serif;
--aforo-font-size-base: 14px;
/* Radius */
--aforo-radius-sm: 4px;
--aforo-radius-md: 8px;
--aforo-radius-lg: 12px;
}Unset variables fall through to the brand kit and then to the neutral default. You only need to declare the ones you want to override.
Per-widget overrides#
For widget-specific tuning that shouldn't propagate globally, use the themeOverrides prop:
PricingSection.tsx
<AforoPricingCard
tenantSlug="acme"
embedKey="embk_live_…"
themeOverrides={{
primary: '#7C3AED',
onPrimary: '#FFFFFF',
radius: '16px',
}}
/>Dark mode#
Three modes via the theme prop:
theme="auto" (default) — reads prefers-color-scheme: dark from the user's OS and switches automatically.theme="light" — force light palette.theme="dark" — force dark palette (background #1A1A2E, text #F8FAFB).
<AforoInvoiceList
tenantSlug="acme"
embedKey="embk_live_…"
bridgeToken={token}
theme="dark"
/>Loading skeletons honor prefers-reduced-motion: reduce — the shimmer animation pauses for users who've opted out of motion. No prop needed; it's automatic.
RTL support#
The widgets use 100% logical CSS properties (margin-inline-start, padding-inline-end, inset-inline-start) — no margin-left or padding-right. Setting dir="rtl" on the page or a containing element flips the layout automatically.
<!-- Set dir at the document level or on a containing element -->
<html lang="ar" dir="rtl">
<body>
<div
data-aforo-widget="pricing-card"
data-tenant-slug="acme"
data-embed-key="embk_live_…"
data-locale="ar-AE"
></div>
</body>
</html>Currency and date formatting use the standard Intl API — pass locale (default: navigator.language) to override the formatting locale for amounts and dates.
Tailwind / MUI / plain CSS#
Every widget class is namespaced (aforo-w-pc-* for pricing card, aforo-w-sb-* for subscribe button, etc.) so the widgets won't collide with your CSS. We never use !important — your own styles can override anything via normal specificity.
Tailwind
@layer base {
:root {
--aforo-color-primary: theme('colors.indigo.600');
--aforo-color-on-primary: theme('colors.white');
--aforo-font-family: theme('fontFamily.sans');
--aforo-radius-md: theme('borderRadius.lg');
}
}MUI (v5+)
import { useTheme } from '@mui/material';
export function AforoThemeBridge({ children }: { children: React.ReactNode }) {
const theme = useTheme();
return (
<div
style={{
// Map MUI palette → Aforo CSS variables
['--aforo-color-primary' as any]: theme.palette.primary.main,
['--aforo-color-on-primary' as any]: theme.palette.primary.contrastText,
['--aforo-font-family' as any]: theme.typography.fontFamily,
}}
>
{children}
</div>
);
}Plain CSS
Drop the CSS variables block in your stylesheet at :root and you're done. The widgets pick them up automatically — no JavaScript needed.
Live preview your theme in Embed Studio → Widgets → Configure. The mock preview pane shows the actual widget with your brand kit applied, before you deploy any embed code.