Developers

  1. Using the theming system
  2. Writing themes
  3. Art Direction
  4. How theming works
  1. Using the theming system
  2. Writing themes
  3. Art Direction
  4. How theming works

Using the theming system

RHDS' theming system is a high-level expression of the lower-level components of the system, tokens, and elements, and in turn, it factors into the development of the highest level design system component, patterns. In order to use the theming system, then, developers must already be familiar with our tokens and elements. In other words, theming is the developer's process of orchestrating design tokens with elements, particularly by way of themeable container elements

Themeable containers

In RHDS, special provider elements such as <rh-surface>, <rh-card>, <rh-tabs>, and others are considered themeble containers. A themeable container is an element that can have custom classes attached which provide values for the relevant CSS color properties in a custom theme.

A common pattern for a themeable container is the full width band. For example, a <rh-surface> may be used as a full-width container and provide the Bordeaux theme values to a set of 3 cards in a grid:

Example

Card

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam eleifend elit sed est egestas, a sollicitudin mauris tincidunt. Pellentesque vel dapibus risus. Nullam aliquam felis orci, eget cursus mi lacinia quis. Vivamus at felis sem.

Call to action

Card

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam eleifend elit sed est egestas, a sollicitudin mauris tincidunt. Pellentesque vel dapibus risus. Nullam aliquam felis orci, eget cursus mi lacinia quis. Vivamus at felis sem.

Call to action

Card

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam eleifend elit sed est egestas, a sollicitudin mauris tincidunt. Pellentesque vel dapibus risus. Nullam aliquam felis orci, eget cursus mi lacinia quis. Vivamus at felis sem.

Call to action

HTML

Copy to Clipboard

CSS

Copy to Clipboard

The color-palette attribute

The color-palette is a fundamental aspect of the theming system. The attribute is available on specially designated provider elements that actively define a [color palette][palettes], while their children passively accept their background color and text color. The color-palette can be set to six possible values lightest, lighter, light, dark, darker and darkest.

Theming whole pages

To theme an entire page, you may replace the <main> element with an <rh-surface role="main">. This ensures that computed theme tokens propagate to all children, while maintaining the proper landmark semantics.

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>My Themed Page</title>
  </head>
  <body>
    <rh-surface role="main" color-palette="darker">
      ...
    </rh-surface>
  </body>
</html>

Theming individual sections

You may also theme particular sections, giving them a contrasting color palette. Read more about the design considerations for contrasting sections on the color-palettes page.

<body>
  <rh-surface role="main">
    <section aria-labelledby="heading">
      <h2 id="default">Default palette</h2>
      <p>Default color palette (i.e. lightest)</p>
    </section>
    <rh-surface role="section"
                aria-labelledby="darker"
                color-palette="darker">
      <h2 id="darker">Darker palette</h2>
      <p>Contrasting color palette (i.e. darker)</p>
    </rh-surface>
  </rh-surface>
</body>

Writing themes

When theming pages, sections, or elements, developers should carefully review the designs they received, and seek to make as few customizations as necessary. The ideal order-of-operations is as follows:

  1. Search for an appropriate or approximate pattern
  2. Use element-specific tokens to customize an element e.g. --rh-card-header-background-on-light
  3. Use theme tokens to customize the target e.g. --rh-color-interactive-primary-default-on-light
  4. Use element CSS Shadow Parts for greater control

Avoid setting values for CSS custom properties beginning with an _. These should be considered "private" and may change at any time without warning

Art Direction

Art direction is the process of selecting art assets based on the context in which they are viewed. In terms of theming, art direction means choosing or modifying graphics based on the surrounding theme or color palette. There are two ways to approach this:

  1. Dynamic graphics
  2. Alternating graphics

Dynamic graphics

Page authors can create dynamic graphics which respond to their surrounding theme by using inline SVG that references theme tokens. For example, this svg graphic uses the --rh-color-border-interactive theme token to style a rectangle.

<svg slot="header" width="80" height="80">
  <rect fill="var(--rh-color-border-interactive)"
        fill-opacity="0.1"
        stroke-dasharray="4"
        stroke-width="1"
        stroke="var(--rh-color-border-interactive)"
        width="80"
        height="80"/>
</svg>

This approach does not work with svg loaded through the <img> tag, or with raster graphics, however another approach is in development which could help:

Alternating Graphics

Developers can accomplish graphical art direction in their themed pages by swapping linked or raster graphics depending on the context. For example, if the Red Hat logo appears on sections with botyh light and dark backgrounds, developers should carefully choose the graphic which matches the background.

<rh-surface role="section"
            aria-labelledby="products"
            color-palette="darkest">
  <h2 id="products">Products</h2>
  <img alt="Red Hat Enterprise Linux"
       src="/assets/logos/products/rhel-on-dark.svg">
</rh-surface>

<rh-picture> Planned

We're now in the early stages of developing an art direction element for these kinds of scenarios, tentatively named <rh-picture>. The following speculative code block shows how the element may be used in the future to enable responsive themable graphics

<rh-surface role="section"
            aria-labelledby="products"
            color-palette="darkest">
  <h2 id="products">Products</h2>
  <rh-picture>
    <source srcset="/assets/logos/products/rhel-on-dark.png" color-theme="dark"></source>
    <source srcset="/assets/logos/products/rhel-on-light.png" color-theme="light"></source>
    <img src="/assets/logos/products/rhel.png" alt="Red Hat Enterprise Linux">
  <rh-picture>
</rh-surface>

Join the discussion

How theming works

We developed our theming system with web standards in mind, aiming for it to use CSS over JavaScript as much as possible. At the current state of the art, we found that some JavaScript is required for the system to work correctly, but in the near future, we expect to be able to remove all or nearly all of that JavaScript, and have the theming system work entirely (or almost entirely) through plain CSS. The following explains how the system currently operates, how element authors can hook into it, and what the future holds for theming.

Current implementation - Context Protocol

RHDS' Theming system is primarily about styles. Tt currently relies on JavaScript to work, by way of the context protocol developed by the web components community to support passing data between components.

Our system utilizes this protocol with the setting of the color-palette attribute on a provider element which makes its context data (in our case, light or dark) to it's children. By doing so we can ensure accessible colors are applied given any possible change in context value higher up in the DOM tree.

This is important not only for helping us maintain our own design guidelines, but also for accessibility compliance and enabling great experiences for all our users.

The context protocol is enabled by two reactive controllers: the provider controller, and the consumer controller. These controllers work in tandem to alternately provide the context data, or consume it as a child element. Custom elements can implement the consumer, the provider, or both, depending on the needs of that particular element.

Providers

Custom Elements that implement the provider controller are elements that provide their own context, overriding that of their parent. Elements such as <rh-surface>, <rh-card>, and <rh-accordion> are examples of such context providers. If a provider contains a set color-palette attribute, it will override any parent context, and pass its context on to its children.

To make your element a color context provider,

  1. First import the provider controller.
  2. Then add the @colorContextProvider() decorator to a property with the attribute color-palette which is the type ColorPalette.
import { LitElement } from 'lit';
import { customElement } from 'lit/decorators/custom-element.js';
import { property } from 'lit/decorators/property.js';

import {
  colorContextProvider,                                    // 1
  type ColorPalette,
} from '@rhds/elements/lib/context/color/provider.js';

@customElement('rh-provider')
export class RhProvider extends LitElement {
  @colorContextProvider()                                  // 2
  @property({ reflect: true, attribute: 'color-palette' })
  colorPalette?: ColorPalette;
}

Read "[What are color palettes][palettes]" for more information about the six available color-palettes.

Consumers

Context consumers are elements that passively consume the parent context. Elements such as <rh-cta>, <rh-badge> and <rh-tag> are examples of consumers.

To make your element a color context consumer,

  1. First import the classMap Lit directive, and the consumer controller
  2. Then add the @colorContextConsumer() decorator to a private property of on which is the type of ColorTheme.
  3. Add a classMap that implements the shadow class in your render method
  4. Use the theming tokens in your element's shadow styles, being sure. to select the element which has the .on.light/.on.dark classes
TypeScript
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators/custom-element.js';
import { classMap } from 'lit/directives/class-map.js';  // 1
import {
  colorContextConsumer,
  type ColorTheme,
} from '@rhds/elements/lib/context/color/consumer.js';

@customElement('rh-consumer')
export class RhConsumer extends LitElement {
  @colorContextConsumer() private on?: ColorTheme;       // 2

  render() {
    const { on = 'light' } = this;                       // 3
    return html`
      <div id="container"
           class="${classMap({ on: true, [on]: true })}">
      </div>`;
  }
}
CSS
#container {
  color: var(--rh-color-text-primary);
  background: var(--rh-color-surface);
}

What the @colorContextConsumer decorator does, in addition to participating in the context event system, is apply a stylesheet from @rhds/tokens/css/color-context-consumer.css.js to the element's shadow root. That stylesheet selects for well-known class names .on.light and .on.dark, and applies values to the theming tokens, depending on the context received.

.on.light {
  --rh-color-text-primary: var(--rh-color-text-primary-on-light, #151515);
   /* etc... */
}

.on.dark {
  --rh-color-text-primary: var(--rh-color-text-primary-on-dark, #ffffff);
   /* etc... */
}

For more information on the significance of the context values (i.e. ColorTheme), read "Background".

Future state - style queries Planned

In a not so distant future, we will be able to replace the context protocol completely and remove this code by implementing the web standard container style queries.

In anticipation of this upcoming browser feature, we attempt to ensure that our theming system as implemented today using context can easily replaced with style queries in the near future.

Note, the examples in this section are hypothetical, the final markup and styles may not be the same

In addition to reducing the javascript payload of the design system, style queries will allow authors to attach surface styles to any element. For example, Your page markup declares a custom surface with some classes:

<div class="custom-surface dark">
  <rh-cta href="#">GO!</rh-cta>
</div>

And your document CSS sets the desired color context:

.custom-surface {
  container: surface;
  &.dark { --rh-background-context: dark; }
  &.light { --rh-background-context: light; }
}

And you would declare the import a shared stylesheet which activates the theming system, similarly to how elements import @rhds/tokens/css/color-context-consumer.css.js

@container style (--rh-context-background: dark) {
  --rh-color-text-primary: var(--rh-color-text-primary-on-dark)
}