Photo by Magda Ehlers from Pexels

A scalable naming convention for style-variables

Brett Uglow
DigIO Australia

--

Naming things well is one of the hard problems in computer science. I would argue it is even harder in the world of visual design. The reason it is hard is because visual designs continually change, so the UI code has to keep changing too. This article looks at an approach I’ve started using for websites, but would work equally well for designers and developers of any graphical user interface (GUI).

These are the problems that developers and designers face:

  • There are lots of ways to name things, so it’s hard to pick one way.
  • Designers don’t always provide a scalable naming system to start with, as theming isn’t usually a day-one requirement.
  • A convention that works for one theme often needs to be changed when a new theme is introduced (e.g. light and dark themes).
  • A convention that works for a small applications and websites will start to break as the GUI grows.

Requirements for a naming convention

A good naming convention should satisfy the following requirements:

  1. It should scale well for small-to-large themes. The variable names should make sense regardless of the number of variables in the theme. For example, darkColour is not as scalable as backgroundColour (which won’t have to change when a dark-theme is introduced).
  2. Variables should be semantic in all theme instances. This means that a variable like textDark should represent dark-coloured text inside all themes. If it does not, then the variable name should be changed.
  3. It should support colours, fonts and brands — the main facets of an application theme.

Naming conventions that do NOT completely work

The following examples use CSS-variable syntax for illustrative purposes, but the arguments apply to every theming approach.

  1. Using actual colours/sizes in variable names.
.light {
--c-white: #F0F0F0;
--c-red: #BB532E;
--font-12: 12px;
}

This doesn’t work because when a new theme is introduced, what do you do with -c-white? Do you rename it (and rename all references in your code) to -c-black(for example), or do you change it’s value to -c-white: black? (You may laugh at this latter suggestion, but a developer actually proposed this approach to me as a serious solution. I patiently explained why that wasn’t a great approach because it is not semantic (as per requirement #2)).

2. Using scientific units for variable names.

.light {
--c-grey-micro: #eee;
--c-grey-mega: #333;
--font-milli: 8pt;
--font-deca: 12pt;
}

This approach is both hard to remember (do you know whether deca is bigger than milli?) and doesn’t have much meaning for variables that do not sit on a continuum (e.g. red and grey). It fails requirement #2.

3. Using any non-semantic scale (cities, animals, military alphabet, greek alphabet).

This approach suffers the same problem as approach 2 — you cannot expect someone looking at your code to know that font-charlie is bigger (or smaller?) than font-tango. The meaning of the variable name is not clear — it also fails requirement #2.

4. Using relative scales that need to change when new themes are introduced.

.light {
--c-background: #fff;
--c-text-light: #ccc;
--c-text-dark: #333;
--font-small: 8pt;
--font-medium: 12pt;
--heading-1: 24pt;
}

While the font sizes are semantic and scalable across themes, the colour variables are not. What happens to --c-text-light when a dark theme is added?

.dark {
--c-background: #000;
--c-text-light: #333; /* This is darker than --c-text-dark! */
--c-text-dark: #ccc;
}

A scalable approach

Rather than naming colour variables based on their brightness-compared-to-an-arbitrary-colour (e.g. text-dark/text-light), let’s see what happens when we name them based on their contrast to the background colour:

.light {
--c-background: #fff;
--c-text-low-contrast: #ccc;
--c-text-high-contrast: #333;
}
.dark {
--c-background: #000;
--c-text-low-contrast: #333;
--c-text-high-contrast: #ccc;
}

The text colour variable names are still semantic! Another great aspect of this approach is that it works for component variable names (e.g. bg-component-disabled, bg-component-disabled-low-contrast), foreground and background colours, borders and fonts.

What about size-related variables (e.g. fonts and borders)?

My suggestion would be to use a relative scale for when there is a limited set of values, or a percentage-based scale when there are lots of values. For example:

.light {
/* Limited set of values: */
--border-thin: thin solid #ccc;
--border: 2px solid #ccc;
--border-thick: 4px solid #ccc;

/* Lots of values: */
--text-contrast-0: #fff;
/* ... */
--text-contrast-50: #888;
/* ... */
--text-contrast-100: #000;
}

Brand Support

It can be tempting to treat a brand as a theme — and maybe that is the right approach when there are lots of stylistic-differences between brands. But other times you just need to show a different logo and maybe a brand colour in the app’s header. To support this, you just need a few more variables:

--brand-colour: #abc;
--brand-logo: url('brand.svg')

To determine whether you need a theme or just a few variables, talk to your team to figure out what the branding requirements are.

Example showing everything together

// lightTheme.ts
import colours from './colours.ts' // Contains lots of colours
const colourAlias = {
focusIndicator: colours.blue40,

/* Backgrounds */
bgBody: colours.gray95,
bgContrast5: colours.gray95,
bgContrast10: colours.gray90,
bgError: colours.destructive98,
bgWarning: colours.yellow60_95,
bgWarningAlt: colours.orange60_90,
bgSuccess: colours.green40_98,
bgMessage: colours.blue50_98,
/* Text colours */
textHighestContrast: colours.gray10,
textHighContrast: colours.gray20,
textMediumContrast: colours.gray30,
textLowContrast: colours.gray40,
textVeryLowContrast: colours.gray50,
textLowestContrast: colours.gray80,
textError: colours.destructiveRed,
textWarning: colours.orange60,
textInfo: colours.blue40,
/* Borders */
borderContrast10: colours.gray90,
borderContrast20: colours.gray80,
borderContrast30: colours.gray70,
borderError: colours.destructiveRed,
}
const border = {
highContrast: `thin solid ${colourAlias.borderContrast30}`,
standard: `thin solid ${colourAlias.borderContrast20}`,
lowContrast: `thin solid ${colourAlias.borderContrast10}`,
}
export const lightTheme: DefaultTheme = {
border,
colour: colourAlias,
// ... plus font styles, shadows, animations, ...
}

The border variables nicely demonstrate how to name things in a contrast-specific way.

One more thing…

If you are building a theme, avoid exposing variables that you don’t want people to use. This sounds obvious, but let’s look at an example using styled-components:

// lightTheme.ts
import colours from './colours.ts' // Contains lots of colours
const themeColours = {
fgComponent: colours.white,
headingText: colours.primaryBlue
}
export const lightTheme: DefaultTheme = {
colour: {...colours, ...themeColours}
}

Note that in this example, the theme contains both the “colours” and the “themeColours”. This creates a problem because any components that use the theme can access the colours as if they were really part of the theme — which they are not! This creates a dependency between the component and an implementation-detail of the theme, which may not be obvious until you change themes.

My suggestion is to allow the theme file/class/component to access your base colours/fonts, but only expose colour/font/border-aliases in your theme, so that components are looking just at the theme (not at any base-properties). Now, this only applies to properties that are theme-able. If a variable doesn’t change between themes (e.g. an organisation-wide font-style), then it can be exposed directly to a component.

Summary

A scalable naming convention requires having style-variable names that are:

  • Semantic across all theme instances
  • Use relative-scales (and consider whether a variable is part of a small set or large set of values) rather than unrelated scales (e.g. scientific units)
  • Use an objective reference point for names (such as background contrast for colours) rather than a subjective reference point (e.g. dark, light)

Thanks to the fantastic team at Mantel Group for your feedback and reviews!

--

--

Brett Uglow
DigIO Australia

Life = Faith + Family + Others + Work + Fun, Work = Software engineering + UX + teamwork + learning, Fun = travelling + hiking + games + …