Skip to content

CSS Design System

BuddyPress Statistics 2.0.x uses a theme-aware token system on the front end. The plugin no longer ships with saturated brand defaults; instead, every visible surface, text color, border, and accent is driven by a chain of CSS custom properties that read the active theme’s variables first and fall back to quiet neutral defaults.

The design philosophy is simple: inherit the theme. A bright indigo default looks AI-generated next to a neutral block theme. A muted neutral default looks intentional next to everything, and themes that DO expose their palette will override it for free.


Front-end tokens are defined in public/css/bp-stats-variables.css. It is not a separate style handle: public/css/bp-stats-public.css pulls it in with @import url('bp-stats-variables.css'), and the minified build (public/css/min/bp-stats-public.min.css) inlines it. The theme compat stylesheets declare the public handle (bp-stats) as their dependency so they load after the tokens.

The wp-admin screens use a separate --bpstats-admin-* token set; see Admin Tokens below.


Each token has the same structure: theme tokens first, neutral fallback last.

1. BuddyX → --bx-color-*
2. Reign → --reign-*
3. BuddyBoss → --bb-*
4. WP core themes → --wp--preset--color--*
5. Neutral default (gray-scale, NOT indigo / purple / brand)

Not every token has all five links. Some (--bp-stats-bg-tertiary, --bp-stats-border-light) only chain a BuddyBoss token, and a few (--bp-stats-text-light, --bp-stats-border-medium) are plain neutrals.

The comment block at the top of bp-stats-variables.css still lists BuddyBoss first; the declarations below it are the source of truth.

Example - page background:

--bp-stats-bg-primary: var(--bx-color-bg-page,
var(--reign-site-body-bg-color,
var(--bb-content-background-color,
var(--wp--preset--color--base, #ffffff))));

If the active theme is BuddyX, --bx-color-bg-page resolves and the rest of the chain is never read. If it is a neutral WP block theme that publishes --wp--preset--color--base, that wins. If nothing in the chain is published, the final #ffffff keeps the plugin from rendering invisibly.


Token Purpose
--bp-stats-bg-primary Paper / page background
--bp-stats-bg-elevated Elevated card surface
--bp-stats-bg-secondary Recessed background / section divider
--bp-stats-bg-tertiary Sub-recessed background (table headers, etc.)
Token Purpose
--bp-stats-text-primary Headlines, primary copy
--bp-stats-text-secondary Body copy
--bp-stats-text-muted Helper text, legends
--bp-stats-text-light Disabled / placeholder
--bp-stats-text-inverse Text on accent-filled backgrounds
Token Purpose
--bp-stats-border-color Standard divider
--bp-stats-border-light Subtle separator
--bp-stats-border-medium Form field border

--bp-stats-primary / --bp-stats-accent is the one color the plugin uses for emphasis (focus rings, active tab, primary CTA stroke, hero band, streak ring). It chains --bx-color-accent--reign-colors-theme--bb-primary-button-background-regular--bb-primary-color--wp--preset--color--primary, and falls back to currentColor so it picks up the surrounding text color.

--bp-stats-link / --bp-stats-link-hover follow the theme’s link colors (BuddyX, Reign, BuddyBoss) and fall back to the accent.

Restrained colors used only to communicate state, never for decoration:

Token Default Bg variant
--bp-stats-success --bb-success-color, else #15803d --bp-stats-success-bg (10% mix)
--bp-stats-warning --bb-warning-color, else #b45309 --bp-stats-warning-bg
--bp-stats-danger --bb-error-color, else #b91c1c --bp-stats-danger-bg
--bp-stats-info --bb-info-color, else #1d4ed8 --bp-stats-info-bg

Derived from the accent + neutrals so charts read as part of the theme:

Token Source
--bp-stats-chart-1 accent
--bp-stats-chart-2 65% accent + 35% muted text
--bp-stats-chart-3 info
--bp-stats-chart-3-alt info @ 60% opacity
--bp-stats-chart-4 success
--bp-stats-chart-5 warning
--bp-stats-chart-6 danger
--bp-stats-chart-7 50% accent + 50% info
--bp-stats-chart-8 70% info + 30% success

Token names follow --bp-stats-shadow*, --bp-stats-tint-*, --bp-stats-overlay-*. Tints are produced with color-mix( in srgb, … ) so they re-tint automatically when the accent or surface changes.


When a theme’s token namespace does not match the chain (BuddyX / Reign / BuddyBoss / WP core), the plugin ships a per-theme stylesheet under public/css/themes/ that remaps --bp-stats-* onto whatever tokens that theme does expose.

Each compat stylesheet is registered for every handle in the map and conditionally enqueued by Bp_Stats_Public::enqueue_styles() on group, member activity and Statistics pages:

  1. The plugin reads get_template() (the active parent theme’s slug).
  2. It looks the slug up in a filterable map (default ships 'buddyx' and 'buddyx-pro' both pointing at bp-stats-theme-buddyx-family).
  3. If a match is found, the matching stylesheet is enqueued with the public bp-stats stylesheet (which carries the tokens) as its dependency, so the remapping wins.

Default map (in public/class-bp-stats-public.php:167):

array(
'buddyx' => 'bp-stats-theme-buddyx-family',
'buddyx-pro' => 'bp-stats-theme-buddyx-family',
)

The shipped stylesheet public/css/themes/bp-stats-theme-buddyx-family.css is shared by both buddyx and buddyx-pro (same token namespace) and covers light + dark mode.

Three steps:

  1. Write the compat stylesheet at public/css/themes/bp-stats-theme-{handle}.css. Override --bp-stats-* tokens onto the theme’s real variables.
  2. Register the mapping via bp_stats_theme_compat_map:
add_filter( 'bp_stats_theme_compat_map', function ( $map ) {
$map['my-theme-slug'] = 'bp-stats-theme-my-theme';
return $map;
} );
  1. (Optional) Override the URL if the stylesheet ships from your own plugin / child theme:
add_filter( 'bp_stats_theme_compat_url', function ( $url, $handle ) {
if ( 'bp-stats-theme-my-theme' === $handle ) {
return get_stylesheet_directory_uri() . '/bp-stats-compat.css';
}
return $url;
}, 10, 2 );

(Optional) Use the bp_stats_after_theme_compat_enqueue action to enqueue companion JS / fonts for your theme.

Full hook reference: Hooks & Filters → Theme Compatibility Hooks.


There is no prefers-color-scheme query; dark mode follows the host theme’s own switch. bp-stats-variables.css has one root override for these triggers:

html.dark-mode,
body.dark-mode,
body.dark-scheme,
html.dark,
[data-theme="dark"],
.buddyx-dark-mode,
.bb-dark-mode { ... }

Inside it, surface, text and border tokens are set to explicit dark hex values rather than chained through theme tokens, because --bx-color-* / --reign-* / --bb-* stay light unless the theme itself is in dark mode. BuddyX and Reign native dark mode is already covered by the light :root chain, since those themes flip their own tokens. Accent and status hues are not overridden, so they keep inheriting the theme’s colors.

Because this single override flips the tokens for the whole plugin, components should not add per-component .buddyx-dark-mode .bp-stats-card { ... } color rules; use the tokens instead.


The wp-admin screens (?page=bp-stats-settings) do not use --bp-stats-*. They use a fixed --bpstats-admin-* palette (accent, surfaces, text scale, semantic colors, radius, shadow, spacing) declared at the top of assets/css/admin.css, which Bp_Stats_Admin_Panel::enqueue_assets() enqueues as the bpstats-admin handle.

  • Token roots. The tokens are declared on .bpstats-admin (the page wrapper, also .wbcom-bpstats) and, since 2.0.1, on .bpstats-toast-host and .bpstats-confirm-backdrop. assets/js/admin.js appends toasts and confirm dialogs to <body>, outside the wrapper, so without their own token roots every var() resolved empty and the dialog rendered transparent. Any new body-appended surface needs to be added to that selector list.
  • Dark mode. Admin dark tokens are overridden only under theme body classes (body.dark-mode, body.bb-dark-mode, body.bx-dark-theme, body.buddyx-dark-mode) scoped to .wbcom-bpstats, .bpstats-toast-host and .bpstats-confirm-backdrop. Admin CSS must never use @media (prefers-color-scheme: dark): wp-admin itself does not follow the OS setting.
  • Links vs buttons. Link color is applied with .bpstats-admin a:not(.bpstats-btn) (since 2.0.1), so <a> elements styled as .bpstats-btn* buttons keep their button colors.
  • Toolbar. .bpstats-toolbar (filter bars above charts and tables) is defined once in admin.css. Fields are capped at 320px (.bpstats-toolbar__field--grow removes the cap) and selects, date inputs and action buttons share a 40px control height. Analytics filter bars (.bpstats-toolbar.bp-stats-dashboard-filter, .bpstats-toolbar.bp-stats-filter-bar) only add a bottom divider; do not redeclare layout, labels or controls there.
  • Settings cards. Form-table and save-bar alignment inside settings cards (.bpstats-settings-main .bpstats-card ...) lives in assets/css/admin-pulse.css, the shared panel stylesheet (bp-stats-admin-pulse handle), including the 640px stacked-row rule.

After editing any admin or public stylesheet, regenerate the min/ and rtl/ variants with grunt (the rtlcss + cssmin tasks).


:root {
--bp-stats-primary: #0ea5e9;
--bp-stats-accent: #0ea5e9;
}
:root {
--bp-stats-bg-elevated: var(--my-theme-card-bg);
--bp-stats-border-color: var(--my-theme-border);
}

The token chain is how things color, not how things layout. Components still use familiar BEM-ish classes:

<div class="bp-stats-card">
<div class="bp-stats-card__head">...</div>
<div class="bp-stats-card__body">...</div>
</div>

If you need to restyle a specific component, target its class with normal CSS specificity - the tokens give you a clean override path for color decisions without having to fight the class selector.


  1. Inherit the theme. The default visual identity is whatever the active theme says it is - no plugin brand color forced on the page.
  2. Quiet neutrals as defaults. When a theme exposes nothing, the plugin reads as a calm gray UI, not a bright AI-default brand.
  3. One accent for emphasis. Active tab, primary CTA, focus rings, the hero band, the streak ring - all read from one accent so the plugin reads as a single product across surfaces.
  4. Status hues communicate state. Green / red / yellow / blue only appear when they mean something (active / dormant / at-risk / info). They never decorate.
  5. Charts derive from accent + neutrals. The chart palette is built with color-mix() from the same accent that drives the rest of the UI, so a theme swap re-tints every chart for free.