Configure the provider that supplies themes, icons, routing, and app-level Pluma behavior.
PlumaProvider provides context to all Pluma components. More specifically, it:
It is required to render PlumaProvider at the root of your app.
A theme object provides all the tokens and styles for a theme. It can be passed to PlumaProvider
as the theme prop:
Full theme objects come from Pluma — import lightTheme or darkTheme from
@customerio/pluma-components/css. To change some tokens in part of your app, create a
partial theme.
PartialPlumaTheme is the shape of a theme that sets only some of the tokens. Besides
createPartialPlumaTheme, usePlumaTheme() in React, or @consume(PlumaThemeContext) in Ember, returns
one when a nested PlumaProvider has merged its theme over an ancestor's. It's a theme prop value like
any other, so you can pass it straight into another PlumaProvider — which is what the microfrontend
guidance below asks you to do. That result already includes the full theme it was merged over, so passing
it on does not nest one partial theme inside another.
PlumaProvider will render a div element and attach the theme's class name to it,
which adds all the theme's CSS variables to that container.
If you need to add additional classes to that element,
add them to PlumaProvider, which will forward them to the div:
If you don't need to render the theme container - for example, when using Pluma Components inside a
microfrontend where the root app already has a theme - you can prevent PlumaProvider from
rendering it with the shouldRenderContainer argument:
Note that it is still recommended to provide a theme, even to a PlumaProvider within a nested
microfrontend. You should retrieve the theme from the root app and pass it into the microfrontends, in case
any code or component needs to access the theme's variables via context.
A partial theme sets only some tokens and takes the rest from the theme it is nested in. Create one with
createPartialPlumaTheme from @customerio/pluma-components/css/partial-theme, in a vanilla-extract
.css.ts file. It runs in your vanilla-extract build, so it is available to React apps with that build
only; Ember apps can't call it.
Pass it as theme to a PlumaProvider nested inside one that already has a full theme:
Nest a partial theme directly inside a Provider with a full theme. Don't nest a partial theme inside another partial theme; to combine changes, put them all in one partial theme.
The partial theme changes every token it sets and every token that aliases one of them, so setting
radius.4 also changes radius.button-medium. Menus, popovers and tooltips rendered in a portal from
inside the nested Provider get the partial theme too.
The partial theme's colorScheme must match the scheme of the Provider it is nested in. To change color
tokens, create one partial theme per scheme and pick the one for the current scheme:
A theme is not the lever for brand color. To paint a region in your own brand, set the
color.scope-accent-*, color.scope-action-* and color.scope-control-solid-* custom properties on a
wrapper element. That reaches every Pluma component inside the wrapper — Button, Link, a checked
Checkbox, a focused Select, all of them — with nothing to set per component. The action rungs paint the
primary Button and anything else that carries a label on the brand fill; control-solid-bg and
control-solid-mark paint a checked Checkbox, a Radio dot, a Toggle that is on and a Slider's filled
track. A brand that moves the action fill and leaves the control fill out gets a brand-colored Button
next to teal controls. getVarName turns a token into the property name to
assign:
In a stylesheet there is nothing to unwrap, so write the emitted property names directly:
color.scope-action-* carries the same rungs as color.scope-accent-*: text, border, mark,
solid-bg, solid-text, solid-mark, subtle-bg, subtle-bgMix, subtle-border, subtle-text
and subtle-mark. color.scope-control-solid-* carries bg and mark. Set only the ones your brand moves; anything you leave out keeps the theme's
value. subtle-bg and subtle-bgMix are a pair, authored per theme, so a brand that supports both
color schemes sets both — see
Interaction states.
A skin changes component styles, such as corner radius and padding, across your app. It is not a theme: a theme sets token values, and a skin sets component variables. If a skin also changes tokens, such as the font family, put those in a partial theme.
A skin is a vanilla-extract .css.ts file. Scope its rules under an attribute on <html>, put them in
layers.skins, and set a component's privateVars on the class from its classNames that assigns them:
A private variable only takes effect on the class that assigns it. DropdownMenu assigns its radius and padding on the menu content, so the skin above targets that class. Because the names are imported, a renamed variable fails your typecheck instead of silently doing nothing.
Put the attribute on <html> so menus, popovers and tooltips rendered in a portal are skinned too.
A public variable you set on a component, and any CSS in layers.app, still win over a skin.
In addition to the explicit theme prop, PlumaProvider accepts a colorScheme prop that picks
between light and dark tokens at runtime. It accepts:
'light' (default) — apply the light tokens.'dark' — apply the dark tokens.'auto' — follow the user's system preference via prefers-color-scheme. The Provider listens
for changes and updates dynamically.To use colorScheme you also pass themes — an object with light and/or dark theme objects.
The Provider picks the matching one based on the resolved scheme:
themes is consumer-supplied so callers that always pass their own theme don't pull the
canonical light/dark theme CSS into their bundle. Import the canonical themes from
@customerio/pluma-components/css if you want the standard light/dark behavior.
The keys describe the resolved color scheme, not a fixed theme identity. A consumer can put an
alternate dark theme in the existing dark slot without expanding the persisted
'auto' | 'light' | 'dark' preference contract:
When the theme prop is provided, colorScheme and themes are ignored — theme always wins as
an explicit override.
Three contexts are exposed for descendants:
PlumaThemeContext (usePlumaTheme() in React, @consume(PlumaThemeContext) in Ember) — the
effective theme object.PlumaColorSchemeContext (usePlumaColorScheme() / @consume(PlumaColorSchemeContext)) — the
resolved scheme, always 'light' or 'dark'. When colorScheme is 'auto', this is the
system-resolved value. Use this for conditional logic that needs a concrete scheme.PlumaColorSchemePreferenceContext (usePlumaColorSchemePreference() /
@consume(PlumaColorSchemePreferenceContext)) — the unresolved preference
('auto' | 'light' | 'dark'). Used by components like Image that need to
decide whether to swap variants via prefers-color-scheme media queries (auto) or by filtering
upfront (explicit).themes propagates through context, so a nested PlumaProvider can specify only colorScheme
and resolve against an ancestor's themes:
When a nested PlumaProvider has colorScheme (and themes are in scope, own or inherited), the
matching variant wins over a theme inherited from PlumaThemeContext. To extend the inherited
themes — overriding e.g. only dark — pass a partial themes object: it's merged over the
inherited one, so the unaltered variants stay accessible.
If you're planning to use the Icon component, you'll need to pass in an
IconManager instance. The IconManager class comes from the
@customerio/pluma-icons package.
An IconManager is responsible for importing the icon SVG sprite sheet, and attaching it to the
document body. When PlumaProvider receives an IconManager,
PlumaProvider will instruct IconManager to load and attach the sprites when the app
first renders.
The sprites only get loaded and attached when PlumaProvider renders, so it's safe to instantiate
the IconManager instance outside of components or effects. Additionally, a single
IconManager instance will only attach sprites once, even if called multiple times.
In microfrontends, only the root app needs to provide an IconManager. The sprites are attached to
the document body, and are then available globally, to all embedded microfrontends.
Similar to the theme object, it is recommended to pass in the IconManager instance from the root
app's context into the microfrontend PlumaProvider, in case any components need to reference the
IconManager.
Pluma includes link components such as PlainLink and
Link, which may perform navigation events. By default, those
components render plain a tags, which would use the browser's native navigation,
which cause a full page reload.
In a single page application, there is typically a routing library which takes over linking and issues navigation events internally, without refreshing the whole page. While Pluma itself is agnostic towards what an app's routing setup is, it allows customizing the link component, so that consuming apps may still navigate correctly.
A custom component can be defined on PlumaProvider through the linkComponent prop.
There are a few basic requirements the component must adhere to in order to work correctly:
LinkComponent type
import type { LinkComponent } from '@customerio/pluma-components/react';import type { PlumaLinkComponent, PlumaLinkComponentSignature } from '@customerio/pluma-components/ember';forwardRefhref - a string, the target URL for the router navigationreplace - a boolean, which indicates that the transition should
replaceState rather than pushStatea tag, and not a Box
component, as Pluma already wraps the component in Boxes internallyIn React with React Router, this setup might look like:
In Ember, it might look like:
Note: In Ember apps, we typically use the LinkTo component for router links.
That component accepts arguments like @model and @route instead of a @href
string, which is different from what we require for Pluma's link component.
Our recommendation is to use an addon like ember-link
or a custom helper to generate the href string. Invoking a PlumaLink component
might then look like:
While the helper might be:
Certain components may expose a set of default or configuration props that can be
set from the PlumaProvider level. For example, the Snackbar component's
close timeout can be modified like this.
PlumaProvider accepts a componentConfig prop, which is an object with component names for keys.
Please refer to the individual component docs for more information on what configuration is available.
Which color scheme to apply.
'light' (default): apply the light tokens.'dark': apply the dark tokens.'auto': detect the user's system preference via
@media (prefers-color-scheme: dark) and apply light or dark tokens
accordingly. The Provider listens for changes and updates dynamically.Ignored when theme is provided — theme always wins.
An instance of a GlobalThemeManager, which manages global theme state and variables
By default, global variables will live in a <style> element just under
the PlumaProvider.
If a different container is specified, the the <style> element will be
portaled into that container instead.
An instance of an IconManager, which attaches the icon sprites
Whether the ScrollbarProvider should be disabled, preventing it from measuring scrollbar widths.
The component that will be used for Link and PlainLink
Where to portal components like Tooltip into
Whether an "isolation: isolate" CSS class should be applied to the theme container
Whether the provider should override global variables if provided theme version is newer than the current global theme version.
Whether the theme container div (with theme class name) should be rendered
The theme to apply to the provider
The root element to set theme attributes on. Can be a CSS selector string, an HTMLElement, or a function returning an HTMLElement. Defaults to document.documentElement.
The themes to apply when theme is not provided. The Provider picks
themes.light or themes.dark based on the resolved colorScheme and
passes it through the global theme manager (which sets the root data
attributes and, when configured, applies global CSS variables).
Pass these explicitly so consumers that always provide their own theme
don't pull the canonical light/dark theme CSS into their bundle. Import
the canonical themes from @customerio/pluma-components/css if you want
the standard light/dark behavior.
Whether to load baseline styles, which include global styles for the body element.
Whether to load global reset styles (box-sizing, font inheritance, etc.).
Whether the theme variables should be set globally on :root instead
of as a class name on a div container.