Theming
Design System components are themed with CSS custom properties. Override the properties rather than the component's styles directly, and your customisations keep working across releases: the property names are a stable contract, the selectors and declarations behind them are not.
Because the properties are the interface, theming works the same way in Core, React and Rails, and you never need to know a component's class name to theme it. Knowing the property names is enough, so a whole theme can live in one place.
Each component's page lists the properties it exposes — see Button for an example.
Where to put an override
Component properties are declared on :root, :host, so they reach the component by inheritance. That means an override anywhere above the component applies to it.
/* recolour every button on the page */
:root {
--rpf-button-background-color: rebeccapurple;
--rpf-button-background-color-hover: #4c1d95;
}
/* or scope it to part of the page, using your own class */
.checkout-panel {
--rpf-button-background-color: rebeccapurple;
}
/* or target individual components, again via your own class */
.checkout-panel__submit.rpf-button {
--rpf-button-background-color: rebeccapurple;
}
<div class="checkout-panel">
<button class="rpf-button checkout-panel__submit">Pay now</button>
</div>
.checkout-panel and .checkout-panel__submit above are your classes, not Design System ones. You don't need a Design System modifier to theme a component, just somewhere to hang the declaration.
All three work, and the most specific declaration wins in the usual way; a declaration on the element itself beats a value inherited into it.
A few components still declare their initial values on .component-name rather than :root, :host, and are being migrated. Until then, an override on an ancestor won't reach them — target the component's own class instead, as in the third example above.
If the Design System is loaded inside a shadow root
Web components that load the stylesheet inside their own shadow root, the Code Editor for example, need one specific approach: target the custom element.
/* ✅ works: same element the property is declared on */
editor-wc {
--rpf-button-background-color: rebeccapurple;
}
/* ❌ does nothing */
:root {
--rpf-button-background-color: rebeccapurple;
}
The reason is that :root and :host are not two names for the same thing:
| Selector | Matches | In the main document | Inside a shadow root |
|---|---|---|---|
:root | the document's root element (<html>) | <html> | nothing |
:host | the shadow host element, from inside its shadow | nothing | <my-component> |
For any single copy of the stylesheet exactly one of them matches, so they land the properties at different depths. Loaded in the page, they sit on <html>. Loaded inside a shadow root, they sit on the host element, so your :root rule is an ancestor of them, and an inherited value loses to the declaration already on the host.
!important does not help either. For important declarations the tree order reverses, so it moves you further from winning rather than closer.
Adding a custom property
If you're working on the Design System itself rather than consuming it, see Adding custom properties for which kind of property to declare and where.