Better end to end design systems.

published on March 13, 2023

Implementing a consistent brand across various devices and channels has never been easy. The diversity of connected devices and their screen sizes is also not helping simplify the process.

Let’s take a stab at how we could improve this.

Tooling

On top of the above challenges, the tools designer and developers use are very different from eachother. For instance, while being an amazing design tool, Figma works on a very different structure than the html itself. Which bring in subjectivity of implementation on the developer side, as in, what is good implementation, how things should be structured, how they should be styled.

What defines a digital brand?

Update: my buddy Bhu pointed out to me that I have over simplified the definition of a digital brand. So let’s fix that.

There are several dimensions that contribute to users recognising a brand: logo, color palette, typography, imagery and visual style, tone and voice, user experience, brand messaging and probably several others.

From a very practical perspective, an implementation one in code, I claim the above dimensions can be reduced mostly to these: color, type and space are three main traints that define a brand. Everything else will be minor and will not make a signifficant difference on your brand. Let’s have a a lookt at each and how we can improve their implementation today.

What we aim to achive is ease of implementation and maintanance, including good design communication.

Color

The biggest improvement what we can bring in is the single source of truth for colors. We define a set of variables that will hold our colors.

The how is little different though. While we will use CSS variables for reusability, we will also introduce a new color space, at least new to CSS OKLch.

To make things a little simpler to grasp, I changed the colors on this page to OKLch. Let’s take a look at the definitions:


    --color-light-brand-lightness: 52%;
    --color-dark-brand-lightness: 58%;
    --color-brand-chroma: 0.188;
    --color-brand-hue: 249.435;
    --color-light-brand: oklch(var(--color-light-brand-lightness) var(--color-brand-chroma) var(--color-brand-hue));
    --color-dark-brand: oklch(var(--color-dark-brand-lightness) var(--color-brand-chroma) var(--color-brand-hue));

    --color-light-secondary-1-lightness: 44.4%;
    --color-dark-secondary-1-lightness: 52%;
    --color-secondary-1-chroma: 0.202;
    --color-secondary-1-hue: 26.47;
    --color-light-secondary-1: oklch(var(--color-light-secondary-1-lightness) var(--color-secondary-1-chroma) var(--color-secondary-1-hue));
    --color-dark-secondary-1: oklch(var(--color-dark-secondary-1-lightness) var(--color-secondary-1-chroma) var(--color-secondary-1-hue));
  

There is some level of similarity to HSL, but the difference is that OKLch is perceptually uniform. This means that the difference in definition values between two colors is the same, no matter where they are on the color wheel. This is not the case with HSL, where the difference between two colors is not the same, depending on where they are on the color wheel.

Structurally, I defined two sets of colors here: one for light mode and one for dark mode. See the two for the current color mode below.

And here we have some neutral color shades so that we can see the better definition abilities of the OKLch color space really shine.

Beautiful, uniform and predictable. Here’s the source for the neutral shades above.


    --color-light-neutral-1: oklch(10% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-light-neutral-2: oklch(20% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-light-neutral-3: oklch(30% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-light-neutral-4: oklch(40% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-light-neutral-5: oklch(50% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-light-neutral-6: oklch(60% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-light-neutral-7: oklch(70% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-light-neutral-8: oklch(80% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-light-neutral-9: oklch(90% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));

    --color-dark-neutral-1: oklch(90% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-dark-neutral-2: oklch(80% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-dark-neutral-3: oklch(70% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-dark-neutral-4: oklch(60% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-dark-neutral-5: oklch(50% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-dark-neutral-6: oklch(40% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-dark-neutral-7: oklch(30% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-dark-neutral-8: oklch(20% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
    --color-dark-neutral-9: oklch(10% calc(var(--color-brand-chroma) / 10) var(--color-brand-hue));
  

I have left the scale uniform and linear, but it can be customized to any scale you want. For instance, you can have a scale that is darker at the top and lighter at the bottom, or vice versa. You can also have a scale that is not linear, but exponential, or logarithmic, or any other scale you want.

Also, I left a small level of chroma in the neutral shades to match the brand color better.

Type, or better said, type scales

When I build web interfaces they genrally need to be universal, as in to work well on multiple screens. We typically use media queries for such implementations but there is a better way noy to handle text sizes, their scales in our design systems. Have a look at this link Utopia type

What do we have a on that link? A fluid type scale generator. You can set a few parameters like min/max screen size, font sizes and scales for each ends and what the generator will aloow you to do is set your step sizes.

Think of this like setting the size for a p or span or h1 with these steps as a global setting in your css and then not set it ever again.

Sharing below the type scales output from the above link for this web page.


    /* @link https://utopia.fyi/type/calculator?c=320,18,1.2,1350,20,1.333,8,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */

    :root {
      --step--2: clamp(0.70rem, calc(0.81rem + -0.12vw), 0.78rem);
      --step--1: clamp(0.94rem, calc(0.94rem + 0.00vw), 0.94rem);
      --step-0: clamp(1.13rem, calc(1.09rem + 0.19vw), 1.25rem);
      --step-1: clamp(1.35rem, calc(1.25rem + 0.49vw), 1.67rem);
      --step-2: clamp(1.62rem, calc(1.43rem + 0.93vw), 2.22rem);
      --step-3: clamp(1.94rem, calc(1.63rem + 1.58vw), 2.96rem);
      --step-4: clamp(2.33rem, calc(1.83rem + 2.51vw), 3.95rem);
      --step-5: clamp(2.80rem, calc(2.03rem + 3.82vw), 5.26rem);
      --step-6: clamp(3.36rem, calc(2.22rem + 5.67vw), 7.01rem);
      --step-7: clamp(4.03rem, calc(2.38rem + 8.26vw), 9.35rem);
      --step-8: clamp(4.84rem, calc(2.47rem + 11.84vw), 12.46rem);
    }
  

And here’s an example usage, also from the CSS source of this page.


    h1 {
      font-size: var(--step-5);
    }

    h2 {
      font-size: var(--step-4);
    }

    h3 {
      font-size: var(--step-3);
    }
  

This is all that needs to be done to achieve fluid text sizing. No need for any media quesries for this purpose!

Space, or space scales

Similarly to the fluid text sizing above we can also achive something similar with fluid space. Have a look at this link Utopia space

This handy tool allows us to generate spce values for various scenarios and needs, from single step pairs and custom step pair. It also generates the CSS variable definitions we can just paste in our code.


    /* @link https://utopia.fyi/space/calculator?c=320,16,1.2,1350,20,1.414,8,1,&s=0.75|0.5|0.25,2|3|5|8|13,s-l|l-2xl|xl-2xl&g=s,l,xl,12 */

    :root {
      --space-3xs: clamp(0.25rem, calc(0.23rem + 0.10vw), 0.31rem);
      --space-2xs: clamp(0.50rem, calc(0.46rem + 0.19vw), 0.63rem);
      --space-xs: clamp(0.75rem, calc(0.69rem + 0.29vw), 0.94rem);
      --space-s: clamp(1.00rem, calc(0.92rem + 0.39vw), 1.25rem);
      --space-m: clamp(2.00rem, calc(1.84rem + 0.78vw), 2.50rem);
      --space-l: clamp(3.00rem, calc(2.77rem + 1.17vw), 3.75rem);
      --space-xl: clamp(5.00rem, calc(4.61rem + 1.94vw), 6.25rem);
      --space-2xl: clamp(8.00rem, calc(7.38rem + 3.11vw), 10.00rem);
      --space-3xl: clamp(13.00rem, calc(11.99rem + 5.05vw), 16.25rem);

      /* One-up pairs */
      --space-3xs-2xs: clamp(0.25rem, calc(0.13rem + 0.58vw), 0.63rem);
      --space-2xs-xs: clamp(0.50rem, calc(0.36rem + 0.68vw), 0.94rem);
      --space-xs-s: clamp(0.75rem, calc(0.59rem + 0.78vw), 1.25rem);
      --space-s-m: clamp(1.00rem, calc(0.53rem + 2.33vw), 2.50rem);
      --space-m-l: clamp(2.00rem, calc(1.46rem + 2.72vw), 3.75rem);
      --space-l-xl: clamp(3.00rem, calc(1.99rem + 5.05vw), 6.25rem);
      --space-xl-2xl: clamp(5.00rem, calc(3.45rem + 7.77vw), 10.00rem);
      --space-2xl-3xl: clamp(8.00rem, calc(5.44rem + 12.82vw), 16.25rem);

      /* Custom pairs */
      --space-s-l: clamp(1.00rem, calc(0.15rem + 4.27vw), 3.75rem);
      --space-l-2xl: clamp(3.00rem, calc(0.83rem + 10.87vw), 10.00rem);
      --space-xl-2xl: clamp(5.00rem, calc(3.45rem + 7.77vw), 10.00rem);
    }
  

Here’s a usage example also from the CSS source of this page.


    pre {
      font-family: var(--font-monospace);
      font-size: var(--step-0);
      padding-top: var(--space-s-m);
      overflow-x: scroll;
      margin: 0;
      padding: 0;
    }
  

Here’s a usage example also from the CSS source of this page. The size of the top padding for the pre element will fluidly adapting to the screen size it is viewed on.

Neat small feature in the Utopia generators: they also provide the link to prefilled form that have been used before when generating the CSS. Quite nice!

That’s all for today

We have looked at how to define colors in the OKLch color space, how to use Utopian type and space scales to reduce the complexity of our CSS.

Ah, one more thing

You may wonder why I chose the end to end in the post title. I did that, because we can use this not only in CSS, but also in Figma. Folks who created the Utopia generators have created a Figmap plugin, find more about that here. It basically allows for us to use the same size scales in Figma as well.

On the flip side, Figma doesn’t suppor OKLch color space. Maybe I can do something about this in the future.