How I Designed My Personal Design System
Harmonious design is foundational to me.
Everything I produce follows a coherent system of design principles. I have distilled that system into a set of rules.
This is my personal design system.
Nothing is arbitrary.
Typographic Scale
To begin, I had to decide on a scale that resonated with me. I wanted the information hierarchy to be easy to navigate. Too many levels and the user would get lost; too few, and there wouldn’t be sufficient granularity to structure the data.
Triangulating with a 3-level hierarchy felt like a perfect balance, so I settled on that.
After exploring several ratios and proportions, I settled on a pure tritonic (also called “tritonic modular”) scale for text, and the Fibonacci sequence for mostly everything else.
The tritonic scale is derived by dividing an octave (2:1) into 3 equal steps. It means 3 steps up, and the base size doubles (visual octave), while 3 steps down halves the base size (visual sub-octave).
My design system includes both digital and print media, each with its own set of constraints.
To establish my type scale, I first had to decide on my base font size.
For digital, a popular size is 16 px. I saw no objection and agreed to use the same.
For print, a standard type size is 12 pt. Again, I saw no objection and agreed to use the same.
I then calculated a scale for each media base using the tritonic scale and this formula:
ratioPerStep = ∛2 ≅ 1.259921For each step, we assign a system-wide name.
- For each increment above our base: “step-a#”
- For each decrement below our base: “step-z#”
This naming convention scales indefinitely in both directions, but the media naturally enforces constraints. Unless I was making a billboard to hang off the side of a building, it wouldn’t be practical to use a “step-a100”, for example. That said, I would likely establish a different base for such a use case, but you get the idea.
Additionally, we know that the minimum readable text size on a modern digital device is 12 px, so there is no point in computing anything smaller than that.
Since we’ve already decided that 16 px will be our base size, we can set it to 1 rem and label the next 3 parents as h3, h2, and h1 relative to it.
For sizes smaller than our base, we can label them small, extra small, and so on, but since we have already set our floor at 12 px, our system doesn’t support anything smaller than a “small”, so that’s all we’ll include.
The benefit of this system is that it decouples the HTML elements (e.g., h1, h2, h3, body) and the programming/implementation concept from our design scale.
We are also not limited to the maximum size of whatever h1 is set to. This gives us the freedom to use larger text sizes without forcing exceptions. We can expand the system at any time to include larger font sizes without breaking our naming pattern.
A great example of this is with 404 pages.
It is not uncommon for the “404” to be printed very large. The number itself is the primary content of the page and is therefore displayed in a font size typically much larger than a standard h1 HTML element. To achieve this, however, the design would need to specify an exception if they are relying on a conventional h1 name within the design tool, even though it may in fact be coded as an HTML h1 element; the treatment would differ.
The “Step” system avoids this entirely. The designer picks the font size best suited to their use case, and the engineer decides which HTML element is appropriate. Each person on the project can focus on the core skills they are most familiar with.
Likewise, deciding how a proportional subhierarchy should appear – for example, “Page Not Found” and “Here are some helpful links to help you find what you were looking for” – is already scaled and proportioned.
In the following table, I’ve listed 3 visual octaves and 1 visual sub-octave. The standard h1–3, body, and small body sizes have been labelled and highlighted to show where they sit within the overall scale.
My 404 page uses 2 visual octaves higher for its messaging.
| Step | EM Size | Pixel Size | Name | Example |
|---|---|---|---|---|
| a9 | 8.0rem | 128px | M | |
| a8 | 6.3496rem | 101.5936px | M | |
| a7 | 5.0396rem | 80.6336px | M | |
| a6 | 4.0rem | 64px | M | |
| a5 | 3.1748rem | 50.7968px | M | |
| a4 | 2.5198rem | 40.3168px | M | |
| a3 | 2.0rem | 32px | Heading 1 | M |
| a2 | 1.5874rem | 25.3984px | Heading 2 | M |
| a1 | 1.2599rem | 20.1584px | Heading 3 | M |
| z0a | 1.0rem | 16px | Body | M |
| z1 | 0.7937rem | 12.6992px | Small | M |
| z2 | 0.63rem | 10.08px | M |
Orthogonal Hierarchy
In addition to vertical hierarchy through typographic scale, the system defines an orthogonal hierarchy that operates independently of size.
Changes in letterform case and weight allow elements at the same scale to express different levels of authority.
| Treatment | Example |
|---|---|
| Uppercase | Mass Production State of Mind |
| Title case | Mass Production State of Mind |
| Sentence case | Mass production state of mind |
Luminance Scale
For colour, I wanted to start with a balanced grayscale system before introducing hues.
Many digital colouring systems try to capture all available luminance and hues into equally spaced samples, but that’s not how our human eye perceives light. Our eyes are more sensitive to some types of light than others.
After exploring 6 methods to create a simplified 5-value system, I settled on #757575 as my middle gray. Thankfully, when this shade is used to render text on a white background, it is also AA accessible, so that made the rest of my work much easier.
For digital, I felt black (#000000) was too harsh and contrasty for my taste, so I chose a very dark gray (#222222) to use as a soft black. This became my dark anchor.
I then computed a stepped scale that crossed over middle gray (#757575) and terminated at our soft black (#222222) using the OKLab L-spacing colour system. This produced a scale that accounts for biases in the human eye’s perception of light.
This produced a scale where:
- The darkest shade of gray acts as a soft black
- The lightest shade of gray acts as a soft white
- L46 is middle gray (L46 #757575)
- Text in grays darker than middle gray (≥ L46) works on white (L100 #FFFFFF) backgrounds
- Text in grays lighter than middle gray (≥ L55) works beautifully on soft black (L13 #222222) backgrounds
- L93 on soft black (L13 #222222) appears as white
- L97 on soft black (L13 #222222) appears as bright white
| L-Scale | Hex | Contrast vs. White | Contrast vs #222222 | AA on White | AA on #222222 |
|---|---|---|---|---|---|
| L0 | #000000 | 21.0 | 1.32 | Pass | Fail |
| L13 | #222222 | 15.91 | 1.0 | Pass | Fail |
| L20 | #333333 | 12.63 | 1.26 | Pass | Fail |
| L27 | #464646 | 9.44 | 1.69 | Pass | Fail |
| L37 | #5E5E5E | 6.48 | 2.45 | Pass | Fail |
| L46 | #757575 | 4.61 | 3.45 | Pass | Fail |
| L55 | #8B8B8B | 3.41 | 4.67 | Fail | Pass |
| L64 | #A4A4A4 | 2.49 | 6.38 | Fail | Pass |
| L72 | #BFBFBF | 1.84 | 8.65 | Fail | Pass |
| L85 | #DADADA | 1.4 | 11.38 | Fail | Pass |
| L93 | #EEEEEE | 1.16 | 13.71 | Fail | Pass |
| L97 | #F7F7F7 | 1.07 | 14.85 | Fail | Pass |
| L100 | #FFFFFF | 1.0 | 15.91 | Fail | Pass |
For print, I take a more contrasty approach. Blacks are black, whites are crisp, and middle gray is rarely used. No other semi-tones exist. Instead, line weights and negative space are used strategically to create depth.
Why? Black ink is cheaper, and grays on my laser printer can look washed out. And it looks cool!
Proximity Scale
At this point, it’s worth discussing the Proximity Scale and how it can be used to build relationships and establish a comprehensive information hierarchy.
| Level | Proximity | Description |
|---|---|---|
| 0 | Unity | Content is a continuation of the previous level. Elements feel “bound”. |
| 1 | Close | For tight visual grouping. Think heading + paragraph. |
| 2 | Moderate | Separation of related sub-headings within a section. |
| 3 | Discrete | Separation of similar headings within a broader theme. Signals change of sections. |
| 5 | Unrelated | Separation of unrelated things. Signals a complete break from the previous content. |
There are several ways this scale can be used, depending on how I want to present the information. Again, the goal of this system is to articulate and conceptualize broader themes while not limiting creative flexibility. An overly strict system would be too “templaty”, and that’s not interesting.
These levels can either be used as a multiplier of the current font size to determine the top margin, or the number of shades, steps or visual octaves from the current text to use. Both methods produce different results, and both are valid.
In the case of shades, one extra rule is added, however:
“No two shade levels should appear next to each other.”
There is not enough visual contrast within the scale to perceive the difference without it appearing as an error. When counting shade levels, always skip the first one.
- Example #1: This copy is written in soft black (#222222), and the above table is dark gray (#464646). The table has a “Close” proximity to the copy – but it’s not the copy, so it needs visual separation. Using the Luminance Scale, we start at L13 (#222222), skip the first shade (L20 #333333), then one level up to L27 (#464646) – the exact colour of the table border and header background.
- Example #2: This copy (#222222) has a discrete separation from the side navigation. It’s a shift in cognitive processing from reading this text to navigating. Starting from L13 (#222222), skipping the first value (L20 #333333), then up 3 levels gives us L46 (#757575) – the exact colour of our navigation text.
- Example #3: The Typographic, Luminance, and Proximity Scale headings are all discrete topics within a broader theme. They could be referenced independently. Each of those headings has a top margin that is 3x the font size of the heading, and each subsequent paragraph has a top margin that is 1x the font size to show that it is closely related to that heading.
Pro tip: If you haven’t already noticed, I’m always setting the top margin for a style, never the bottom. This is intentional.
By setting only a top margin, you’re always building on what exists, not on what could be. In the world of HTML/CSS, an overly ambitious bottom margin may need to be compensated with a subsequent negative top margin. If you only ever set top margins, you’ll rarely need a negative margin — ever. While modern browsers have improved significantly, negative margins have historically been a problem for cross-browser compatibility. Avoiding them entirely is the best remedy.
Typically, I set a bottom margin or padding only when I’m dealing with a container and want to ensure a consistent space around its perimeter.