After looking at how Radiance draws Swing components using container color tokens, we’re going back to the surface color tokens available for each container type (active, muted and neutral):

  • containerSurfaceLowest
  • containerSurfaceLow
  • containerSurface
  • containerSurfaceHigh
  • containerSurfaceHighest
  • containerSurfaceDim
  • containerSurfaceBright

Why do we need multiple surface color tokens? Why not provide a single containerSurface and be done with it?

For quite some time now, Radiance supported the concept of decoration area types – recognizing that application menu bars, toolbars and status bars are common examples of special containers found in most user interfaces. These containers create functional grouping of application controls and bring order to complex screens.

This is the main Radiance demo app under the Dust Coffee skin. At the top, we have the window title pane and menubar, rendered in a darker shade of grey. Under that we have the toolbar, rendered in a slightly lighter shade of dark grey. At the bottom we have the status bar, in the same darker shade of grey. The main application content is divided into two panes – control pane on the left and main / general pane on the right.

The visual grouping and separation of application content into distinct decoration areas follows the logical grouping of application content. The so-called “chrome” parts of the UI – title pane, menu bar, toolbars – are grouped to be visually distinct from the main app content. The same applies to the bottom status bar.

This is the same demo app under the Mariner skin. Here, a different design decision has been made. The title pane and the menubar are rendered with dark brown. The rest of the “chrome” – toolbars, control pane on the left, and the status bar are rendered in medium shade of grey. The main content is rendered with a noticeably lighter shade of grey.

Here we have the latest iteration of JetBrains’ IntelliJ, the so-called One Island style. The visual styling of various areas follows the logical grouping of relevant functionality – the title pane at the top, the tool window bars on the left and the right, the left sidebar with project and structure views, the right sidebar with the Gradle view, the bottom tool window with the Run view, and finally the main editor pane in the middle. This new styling uses different shades of grey to convey the logical hierarchy of the different tools and panes, from darker shades along the edges, to medium shades for tool windows, to the lightest shade for the editor.

The same visual grouping and separation is applied in the One Island dark variant, starting with slightly lighter shades of dark grey along the edges, to the darkest shade of dark grey for the editor.

The Claude Desktop app is another example of staying with the same desaturated yellow tones, using slighly darker one for the side bar, medium one for the main panel, and the lightest one for the user reply panel in the bottom right.

This is a concept mock of a sidebar by Jackie Brown on X, visually separating the product bar on the left from the inbox / selected product bar to its right. Using a slightly darker shade of grey for the product bar provides a clean separation between the two, without being too distracting.

And finally, the minimalist note taking Weekstack iOS app uses a gradation of shades of grey to separate the days of the week, both in light and in dark mode.

The common thread between all these examples is that this visual grouping and separation is achieved by using a variation of shades (or tones, in the language of Material and Radiance Chroma) of the same main color. Let’s take a look at how the different surface roles look like in Radiance:

These are the surface roles under the Mariner skin, for neutral and muted containers. On the left is the hierarchy of surface roles from lowest to highest, and on the right is the hierarchy from dim to bright.

And here are the same surface roles under the Night Shade skin.

Comparing the Radiance surface color tokens across light and dark containers, it is important to note:

  • The hierarchy of dim to bright will always have the bright token at a lighter tone
  • The hierarchy of lowest to highest will have the lowest token closer to its “side” of the tonal spectrum, and the highest token going “towards” the opposite side of the tonal spectrum. For light containers, it means that the lowest token is the lightest, and the highest token is the darkest. For dark containers, it flips – the lowest token is the darkest, and the highest token is the lightest.

This also works for more “colorful” skins such as Green Magic – all surface color tokens are taken from the same tonal palette, preserving a strong visual connection between them.

This demo app bundled with the Radiance shows the related concepts of decoration areas and surface containment working together. This app has three decoration areas – the light blue destinations on the left, the medium grey thread list in the middle, and the light grey thread on the right. And then, inside the thread panel on the right, this demo is using surface containment – containerSurface role for the overall panel, and containerSurfaceHighest for each one of the smaller nested boxes.

Going back to the main Radiance demo app, it is using the containerSurfaceLow color token for the nested configuration panels in the left-side control pane. This creates a visual separation for everything related to configuring the demo table, without being too distracting (since it’s using the same tonal palette that is used on the overall control pane) – and without the need to define a separate decoration area type for it.

In the next post we’ll take a look at the world outside of user interfaces to see it through the lens of color tokens, containers, and surface containment.

Picking up where the first part ended, let’s take another look at how HCT colors look like across different values of chroma and tone, now keeping hue constant at 300:

The next step is to introduce two related concepts – color tokens and containers. Radiance has three types of containers:

  • active
  • muted
  • neutral

A container has three visual parts:

  • surface
  • outline
  • content

Each one of these parts can be rendered by the following tokens (single or a combination, such as vertical gradient):

  • For surface
    • containerSurfaceLowest
    • containerSurfaceLow
    • containerSurface
    • containerSurfaceHigh
    • containerSurfaceHighest
    • containerSurfaceDim
    • containerSurfaceBright
  • For outline
    • containerOutline
    • containerOutlineVariant
  • For content
    • onContainer
    • onContainerVariant

Let’s take a look at how these are defined and layered:

The top section in this image shows three tonal palettes generated from the same purple hue, but with different chroma values – 40 for active, 18 for muted, and 8 for neutral. The active palette has higher chroma, the muted palette has medium chroma, and the neutral palette has lower chroma. Even though these three palettes have different chroma values, the usage of the same hue creates a visual connection between them, keeping all tonal stops in the same “visual space” bound by the purple hue.

The next section shows surface, outline and content color tokens generated from each one of those tonal palettes. The tokens are generated based on the intended usage – light mode vs dark mode:

  • Surface tokens in light mode use lighter tones, while surface tokens in dark mode use darker tones.
  • Content tokens in light mode use darker tones, while content tokens in dark mode use lighter tones.
  • Outline tokens in light mode use medium tones, while content tokens in dark mode use darker tones.

The last section shows sample usage of these color tokens to draw a sample container – a rounded rectangle with a piece of text in it:

  • The container background fill is drawn with the surfaceContainer color token.
  • The container outline is drawn with the containerOutline color token.
  • The text is drawn with the onContainer color token.

Let’s take another look at this image:

There is a clear visual connection across all three tonal palettes that are generated from the same purple hue, but with different chroma values. This visual connection is then reflected in the final visuals of our containers, across all three types (active, muted and neutral), both in light mode and in dark mode.

The color system provides strong guarantees about contrast ratio between surfaces and content, and at the same time it keeps all container tokens visually connected.

And now we can take the next step – how Radiance components are rendered.

Radiance treats every element as a container, and Radiance draws every element with container color tokens.

For the main content area:

  • The panel with the 3 buttons is a neutral container. Its background is rendered with the containerSurface color token.
  • The selected toggle button is an active container.
  • The enabled button is a muted container.
  • The disabled button is a muted container. The draw logic uses the three xyzDisabledAlpha tokens for rendering the background, the border and the text.
  • All buttons use the same color tokens:
    • containerOutline for the border
    • onContainer for the text
    • A combination of various containerSurfaceXyz tokens for the gradient stops of the background fill
  • What is different between drawing a selected button and an enabled button? The draw logic uses the same tokens (surface, outline and content). The difference is that a selected button is an active container while an enabled button is a muted container. In this particular case, an active container uses a higher chroma value as the seed for its tonal palette, resulting in more vibrant purple colors – while an enabled container uses a lower chroma value as the seed for its tonal palette, resulting in more muted purple colors.
  • What is different between drawing an enabled button and a disabled button? The draw logic uses the same tokens and the same muted container type. The only difference is in the alpha tokens applied to the surface, outline and content color tokens during the drawing pass.

For the title area, the application of color is the same:

  • The background is rendered with a gradient that uses a number of containerSurfaceXyz color tokens
  • The text and the icons are rendered with the onContainer token

Finally, the window pane border is rendered with a combination of containerSurface and containerOutline / containerOutlineVariant color tokens.

And one last thing – these two UIs are rendered with the same tokens, applying the same container types to the same elements (buttons, title pane, panels). The only difference is the underlying mapping of tokens in light and dark mode:

  • Active container in light mode maps surface color tokens around tone 80, while in dark mode the same tokens are mapped around tone 40. The same distinction applies to outline and content tokens.
  • Muted container in light mode maps surface color tokens around tone 85, while in dark mode the same tokens are mapped around tone 32. The same distinction applies to outline and content tokens.
  • Neutral container in light mode maps surface color tokens around tone 95, while in dark mode the same tokens are mapped around tone 26. The same distinction applies to outline and content tokens.

In the next post we’ll take a look at how the intertwining concepts of color tokens and containers are used to build up the visuals of other Radiance components.

As I wrote in the last post, Radiance 8.0 brings a new color system, code-named Chroma. It’s been 20 years since I started working on Substance back in spring 2005. A lot has changed in the codebase since then, and certainly a lot has changed in the world of designing and implementing user interfaces around us. The most prominent thing has been the meteoric rise of design systems, and the structured approach they brought to managing design consistency at scale.

One of the earliest concepts in Substance was the idea of a color scheme. A color scheme was a set of six background and one foreground colors:

  • Ultra light
  • Extra light
  • Light
  • Mid
  • Dark
  • Ultra dark
  • Foreground

Substance used a combination of these colors to draw the visuals of buttons and other components:

Where the inner fill might be a gradient using extra light, light and mid, the specular highlight would use ultra light, the border would use a gradient with ultra dark and dark, and the text would use foreground.

Over time, the color subsystem in Substance and later Radiance grew more features, and with every customization layer it accumulated, it became more difficult to keep a simple mental model of how colors are defined. And so, about 3 years ago, I started thinking about replacing that color subsystem with a more structured approach. Eventually, I chose to start with the color system that underpins the Material design system, along with customizing it to fit the needs of Radiance.

Material uses a new color space named HCT, which stands for hue+chroma+tone. To introduce these axes, let’s take a look at the illustration of a perceptually accurate color system introduced by professor Albert Munsell in the early 1900s:


Courtesy of Wikipedia. Source by SharkD, derivative work of Datumizer. Licensed under CC BY-SA 3.0 License.

The human eye organizes color by three dimensions – hue, colorfulness, and lightness. In the cylindrical arrangement above:

  • Hue corresponds to the angle on the color wheel. Hue distinguishes between colors such as red, green or purple.
  • Colorfulness is the axis that starts at the center of the cylinder and projects outwards. Colors closer to the center are less saturated and vibrant. Colors close to the edge are more saturated and vibrant – all the while staying with the same hue.
  • Lightness is the vertical axis in this cylinder. Colors at the top layers of the cylinder are lighter, closer to white. Colors at the bottom layers of the cylinder are darker, closer to black. All the while, a vertical “stack” of colors stays with the same hue and the same colorfulness.

This approach is the foundation of the HCT color space created for the Material design system:

  • H is for hue. Hue is in [0..360] range – see below for the visual mapping.
  • C is for chroma (colorfulness). Chroma is a non-negative value, with a different maximum for a particular combination of hue and tone.
  • T is for tone (lightness). Tone is in [0..100] range, where 0 is full black and 100 is full white.

Let’s take a look at how the HCT colors look like across different values of hue and tone, keeping chroma constant at 80:

And this is a look at how the HCT colors look like across different values of chroma and tone, keeping hue constant at 340:

In the next post we’ll take a look at the concept of color tokens, and how they are used to build up the visuals of various components in Radiance.

There are two things common between the websites in these screenshots that I took yesterday.

  1. They are beautifully designed, with great typography, clear branding, all optimized for readability.
  2. I had to install Firefox, Adblock Plus and uBlock Origin, as well as manually select and remove additional elements such as subscription overlays.

The web can be beautiful. Except it’s not right now.