Designing semantic colors for your system
This is a continuation of the article “Creating a Color Palette for Design Systems”. I recommend reading it first to understand how to build palettes, and then move on to this article.
What are semantic colors?
Semantic colors are colors assigned to convey specific meanings or states, such as “positive,” “negative,” “warning,” “primary,” etc. They link the visual elements of an interface to their function or state, making the design process intuitive.
For example, let’s say your primary color is red, and you’ve created a palette for this color with a complete set of tints and shades.

Now you can use the created colors in your components and design elements. For instance, you’ll have red labels and buttons.

Everything seems logical and straightforward: you simply take the necessary tints and shades and apply them where needed. This works well until you need to change the primary color from red to blue during or after the design process. In such a case, you would have to manually update the color in every component.
Semantic colors eliminate this obstacle and allow you to create a system that simplifies the use of colors, especially when switching from one color to another.

Semantic colors offer numerous advantages for designing, implementing, and using a design system. The key benefits include Consistency, Accessibility, Scalability, Flexibility, and Efficiency.
Consistency
Semantic colors ensure consistent use of colors across all components, enhancing both visual and functional coherence.

For example, you can use a single semantic value for all labels, even though the tint parameters might vary depending on the intensity of the color or the desired effect. As a result, when applying a color, you don’t need to worry about the specific tint—you simply select the semantic value.
Accessibility
Semantic colors simplify the creation of accessible interfaces. You can predefine color pairs with sufficient contrast, ensuring reliable color usage.

A semantic accent color always passes the contrast test against a minimal background of the same color. This reduces the need to focus on the nuances of color application and allows you to concentrate more on business logic and design efficiency.
Scalability
Semantic colors provide a clear structure, making it easier to maintain and expand design systems.

You can always extend semantic values while keeping the design consistent and logical, regardless of how the colors in your palette evolve.
Flexibility
Semantic colors allow for quick adaptation of the interface to new themes, modes (e.g., dark mode), or branding changes without the need for a complete overhaul of the color scheme.

This flexibility extends beyond the design phase. Developers can use tokens and variables to implement the interface and easily switch design themes or brand colors by simply updating the semantic values.
Efficiency
Defining colors based on their purpose rather than their appearance helps designers and developers work faster and more efficiently.
A color labeled “Primary.Accent” provides a clear understanding of its purpose for everyone involved. In contrast, a label like “Blue” offers no indication of how the color should be used.
Ultimately, semantic colors become a powerful design tool that simplifies both the creation process and, more importantly, the future expansion and modification of the design.
Different ways
There are several different approaches to creating semantic colors, each with its own advantages and disadvantages. We will explore some key approaches and analyze how each of them works.
Not using semantic colors
One approach is to not use semantic colors at all, and this can even work in practice. For example, this method is used in the CSS framework Tailwind.

In this case, you control the colors of each component and can flexibly change them depending on the conditions and context. This approach provides incredible flexibility since you are not bound by an additional naming system that enforces specific rules.
However, there are significant downsides, such as a lack of design consistency and difficulties in making changes. It becomes quite challenging to update colors across all components if your brand color changes or if you want to make label backgrounds slightly lighter than they are now.
This also impacts the efficiency of the design process. When developing a new component, you constantly have to decide which tint or shade to use, ensure the colors are accessible, and verify that the overall design balance isn’t disrupted.
A system of functional colors
There are semantic color systems based on the functionality of each value. Notable examples of this approach include Radix UI Colors and the colors from the Vercel Geist system.

These systems are exceptionally well thought out, with excellent logic that covers almost every color usage scenario for various components and design situations.
However, such systems have minor issues, mainly related to their flexibility. For instance, if you want to create a divider, it makes sense to use color 6 in Radix UI. But in your specific case, it might be too dark, and color 5 might be more suitable. This, however, breaks the system’s logic because color 5 is not intended for borders. Similar challenges can arise with other values, leading to occasional confusion.
Of course, you can always stick to the strict intended purposes, but in real-world design, there are often many situations where the rigidity of the system becomes an artificial limitation, preventing you from achieving your exact vision.
Additionally, such systems can be challenging to extend. For example, if you want placeholder text to be a soft gray, slightly lighter than value 11 in Radix UI, no such predefined color exists. You’d be forced to create a new value, which falls outside the system’s framework.
Design tokens
The token-based system for semantic colors is the most popular approach. It is widely used in many design systems and has proven to be the most effective and flexible method.

In this approach, you define the naming structure of tokens based on their properties, role, tone, and state. This creates a system that facilitates the creation, usage, and management of colors.

I use this approach in my work, but with a few nuances that make it exceptionally flexible and universal. My naming system and token structure can work for designs of any complexity—from the simplest, using a minimal set of colors with just a few values, to large websites and applications with multiple states, administrative functions, and diverse design themes.
Naming
The foundation of my approach lies in a naming and notation system for semantic colors that covers all possible design tasks. If needed, the naming structure can be easily expanded or, conversely, reduced to a minimum. This flexibility is the cornerstone of a universal system.
There are various approaches to naming semantic colors. Often, these involve using comparative degrees to indicate tone intensity (e.g., strong…weak) or sequential naming like primary, secondary, tertiary, and so on.

While these naming systems can generally fulfill their purpose, they each have significant drawbacks that make them less suitable as a universal solution.
Limitations of existing systems
1. Flexibility and scalability
If you use a notation like strong…weak, you’re limited to six tone options. If you suddenly need more, it becomes an issue. Of course, you could add intermediate values like medium, but this risks breaking the consistency of the notation.
Additionally, such a system—like high…low—is not particularly intuitive in practical use. For instance, if you need to create a primary blue button, which color would be the accent: strong or strongest? Perhaps strongest is dark blue, and strong is light blue. But if strong is light blue, why is it called strong? As a universal solution, this approach often lacks clarity.
2. Sequential naming
A sequential system like primary, secondary, tertiary, etc., works well if you only have three tones—for example, primary for headers and main text, secondary for subtle text, and tertiary for placeholder text. However, if your design is more complex and requires multiple tones for backgrounds, borders, and other utility tasks, this system faces problems with scalability and logic.
Expansion becomes difficult because values beyond tertiary—like quaternary, quinary, senary—are rarely used, hard to remember, and impractical.
Additionally, the logic of this scale breaks down because it’s hard to align the meaning of a value with its name. For example, a primary background, used as the general background of an app, might be white, but a primary button background could be black. This makes it difficult to create a logical and consistent system where the meaning of a name intuitively reflects its tone and use case.
Background, foreground, border
To build the best possible scale and structure for semantic colors, it’s important to determine the specific use cases for these colors. Every interface element has just three properties that are expressed through color: background, foreground, and border. In many design systems, you might encounter different names for these properties:

Sometimes, this can be confusing—for example, both fill and background are used to describe backgrounds. The assumption is that fill refers to the solid colors of components, while background refers to general backgrounds for websites, apps, or groups of components.
I find this distinction unnecessary, as it makes color usage less intuitive. For instance, is the dark background of a card considered a fill or a background? It’s hard to tell.
Using a single term for all types of backgrounds simplifies both usage and understanding. That’s why I call any background, whether it’s for a component or an entire application, Background.

Foreground refers to anything that appears on a background, even if the background is transparent. Therefore, any element color placed on a background—whether it’s text or an icon—is considered Foreground.
In some design systems, Foreground is divided into text and icon, as icons are sometimes slightly lighter than text to create a better balance when used alongside it. However, I believe this difference is too minor to justify creating a separate semantic color scale that would essentially duplicate the Text semantic scale.
For this reason, I always create a single scale for all types of Foreground. Later, I’ll explain how to handle cases where you want icons to be slightly lighter than text.

Borders are a bit simpler. They are typically just outlines, independent of other properties, so the scale is usually named either Border or Stroke, depending on your preference. I prefer Border, as it’s a more commonly used term in CSS and web applications.

Thus, my notation includes only three color properties:
- Background
- Foreground
- Border
This immediately highlights the advantages of using semantic colors. In design tools, it becomes very convenient to adjust different properties since they have clear names. For example, to change text color, you simply update the Foreground color.

Without clear semantic names for properties, this would be more difficult, as the text color might, for example, match the border color. In such cases, you wouldn’t be able to quickly and easily change the text color in a design tool.
Background & Border
I have tested numerous approaches, evaluating their robustness, flexibility, and scalability, and arrived at a single option that meets the strictest requirements while remaining universal for any project.
I developed a notation system with highly intuitive names for backgrounds and borders, reflecting the intensity of the color tone and its meaning.
- ghost
- dimmed
- minimal
- subtle
- medium
- strong
- intense
- +
- accent
- accent-hover
- +
- static
- rich
- deep
- abyss
The first group represents the gradation of tone intensity:

This group covers various levels of color intensity, allowing you to implement backgrounds and borders for any component or overall design. Ghost is the lightest tint, while Intense is the darkest.
You can create your own naming system for background and border scales that better aligns with the logic of your project or team. Personally, I find this set to be highly effective and intuitive.
Typically, there’s no need to use all seven steps of tone intensity. Of course, it depends on the design you’re creating, and often one to three steps are sufficient to achieve your goals.
The second group represents the Accent color, which is usually derived from the base color of the palette and is used for buttons, links, and other accent roles or states.

The name “accent” ensures clarity, indicating that this value is the brightest, most prominent, and its purpose is obvious.
Additionally, you can extend the background and border scales by introducing new values, such as:
- static – for solid colors that remain unchanged regardless of the design theme.
- rich, deep, abyss – various degrees of shades for dark backgrounds.
For example, if your primary color is blue, you might use it for buttons and links. However, if your design includes large primary-colored backgrounds, it’s better to add a slightly darker and less contrasting blue, naming it rich or deep.
Advantages of this naming system
The flexibility of this naming system for backgrounds and borders is unmatched. You can always add any named value tailored to a specific component or use case without breaking the system.
For instance, you might need a special tone for a focus ring on primary buttons. No problem—you could name it primary/border-focus.

Sometimes, you may need a dark background that is black in the light theme and remains dark in the dark theme but slightly lighter than the default body background of the screen. This wouldn’t be a static color but a dynamic one, making it convenient to give it a specialized name.

Foreground
For foreground, I use two notations. One is a scale of color intensity, where, similar to backgrounds and borders, there is an accent color, followed by gradations toward lighter tones.

The accent color is used for any headings, labels, and text, while subtle works well for secondary labels or text. Minimal is an excellent name that also indicates the minimally acceptable contrast for accessibility between the foreground and background. It is typically used for displaying disabled states or tertiary text.
Of course, you can expand the intensity scale by adding other values like dimmed or ghost. For instance, when designing a large heading, part of it can be significantly muted for contrast, using a dimmed tone.

For large text sizes, the contrast ratio will remain acceptable for accessibility despite its lightness.
The second Foreground group consists of specialized colors that can be used based on their purpose. For example:
- heading — In content-focused websites and applications, readability is crucial. Headings can be slightly less contrasting than labels in components.
- body — Similarly, body text in prose, articles, or other content can be slightly muted to reduce contrast with the background and improve readability.
- static — Used for labels or icons that need to maintain the same color in any design theme.
- icon — If it makes sense in your design, you can add a specific foreground for icons to make them slightly lighter than the adjacent text, ensuring better balance in contrast.
This way, the Foreground scale is also flexible and expandable, depending on the needs of your design.

Start with the minimum
Always start with the minimum, especially when working on a project from scratch where many conditions and implementations are still unclear.

Create a set of only the essentials. In the beginning, this might be as simple as a single color like primary/bg-accent for buttons, plus white/fg-accent for labels on them. You can expand and add more as needed later.
Matching backgrounds and borders
I design the semantic scale for backgrounds and borders in a way that, for example, a minimal background pairs naturally with a minimal border, and a subtle background pairs with a subtle border. This makes it effortless to use these combinations when designing components.

Of course, this isn’t a strict rule—you can pair a minimal background with a border of any color intensity if needed. However, having this default alignment simplifies the design process.
It’s also worth noting that it’s not always necessary to create corresponding borders for every background. For instance, if your design includes a minimal background but doesn’t use borders at all, creating border/minimal isn’t required.
Build your naming scale with room to grow
While you may start with only a minimal set of semantic colors, it’s best to design your naming system with future expansion in mind. This is why I’ve aimed to list and demonstrate the maximum number of possible values for all scenarios.
When you eventually need to expand your design system (and this will inevitably happen) or make changes to your design, it’s better to have this flexibility built into your naming system upfront to avoid any complications later on.
Universal structure
I divide all semantic colors into several groups. Four of them are essential for all my design systems, while others are added as needed.
Core and essential groups:
- Surface — Colors for general backgrounds, elevation, layers, popup components, and focus state borders.
- Black & White — Utility colors that either invert depending on the design theme or remain static.
- Input & Link — Colors for input fields, input components, and links in text or other components.
- Primary, Neutral & States — Accent colors or state-specific colors.
Surface
The Surface group includes specialized backgrounds and borders used to represent elevation, overlay layers, and focus states of elements. For example, it covers the general background of a website or application, backgrounds for popups and dropdowns, and focus borders.
In general, these colors help visually structure layers in a design, especially when considering dark themes. For instance, bg-raised in a light theme will match the general background, while in a dark theme, it will be slightly lighter, making the surface stand out.
Default semantic colors for surface:
- bg-body — The general background of a website or application.
- bg-base — Used for card backgrounds or other content, matching the general background.
- bg-raised — Used in dark themes to highlight a surface, making it distinct from the general background.
- bg-overlay — Used for the background of dropdowns, modals, and other popups. In dark themes, it’s usually lighter than the general background.
- bg-overlay-hover — Used for hover states of popup elements, such as a dropdown menu item.
- bg-overlay-active — Used for active states of popup elements, such as a selected item in a dropdown.
- border-focus — Used for borders and focus rings when a button, input, or other component receives focus.
Example variables in Figma for the Surface group:

Black & White
This group includes four colors designed to solve the most basic and utilitarian design tasks, such as component or group backgrounds, borders, dividers, text, headings, and more.
This group consists of four neutral colors:
- Base — Black tones with varying transparency that turn white in dark themes. For example, text will be black in light themes and white (or nearly white) in dark themes.
- Inverted — White tones that, conversely, turn black in dark themes. For instance, if you have a dark background with a button featuring an inverted (white) background, in the dark theme, the background will become white, and the button black—inverted.
- Black — A static black color that remains black in any theme.
- White — A static white color that remains white in any theme.


Input & Link
This group includes semantic colors for implementing input components and links. The Input category encompasses colors for input fields, textareas, selects, checkboxes, and various custom components like toggles and input numbers.
Typical colors for input components in a design System:
- fg-placeholder — Text color for placeholders inside inputs.
- bg-input — Background color for input fields.
- bg-input-focus — Background color for input fields in the focus state.
- bg-input-on — Background color for input components in the “on” state, such as toggles.
- bg-input-off — Background color for input components in the “off” state.
- bg-input-disabled — Background color for input fields in the disabled state.
- border-input — Border color for input fields.
- border-input-focus — Border color when an input field is focused or the cursor is inside it.
I always create a dedicated group of colors for inputs in the design system, ensuring they are independent and easy to modify or extend. This approach simplifies the process of customizing input field borders and backgrounds if your project requires a unique design. For instance, you might want to make the border transparent and the background light gray.
Example variables in Figma for input colors:

I create an extensive set of colors for links, including their hover and active states. These are the colors used for links in both components and text.
Links are often used in navigation components, making the active state particularly important in these scenarios.

As you can see, I create link colors for all possible scenarios. This is very convenient because if a situation arises where a link needs to be placed on a colored accent background, I simply use link/white, and the link color is ready and will work perfectly.
Primary, Neutral & States
This group includes neutral, primary, and state-specific colors.
Neutral
Neutral colors differ from Base or Inverted as they are not transparent and are based on gray tones. They can have a hint of blue or other hues to create harmonious color shades. Neutral colors are used for similar general and utilitarian tasks, such as backgrounds, borders, text, and more.

Primary & States
Primary and state-specific colors are used for accents and, as the name suggests, for various states: success, error, notifications, etc.

This group can easily be expanded with any additional colors needed for your project. For example, if you need a Secondary color and decide on Mint, you can create its semantic colors based on your existing structure:

Now, you have a new semantic color that can be used in your design. This flexibility allows infinite expansion as your project grows, enabling you to add colors based on their state or role, such as secondary, tertiary, or simply by naming the color, like Rose.
Custom
The Custom group is an optional set of semantic colors where I include specific backgrounds, borders, or foreground colors that are unique to a particular project. This is especially useful if the same design system is being used across multiple projects.

Creating this group helps maintain clarity about what is a default in the design system and what has been added as a project-specific customization.
Gradients
The Gradients group contains colors for gradients, specifying their starting, intermediate, and ending points.

This is also an optional group of semantic colors. If your project doesn’t use gradients, this group isn’t necessary.
Charts
In the Charts group, I create colors specifically for data visualization, such as infographics or charts. This group is optional and only necessary if your design requires such elements.

I name the colors for data visualization using numbers to indicate tone when they are used globally across all types of charts. Alternatively, I create subgroups of colors for each specific chart type, such as dedicated colors for Pie or Line charts.
Conclusion
All the color groups and values listed above form a solid foundation for tackling tasks of almost any complexity in any project.
Of course, you can expand this system by adding your own color groups, such as for illustrations or specific interface sections. Likewise, any values within the groups can be extended or customized. It all depends on the project’s needs and requirements.
You can explore how my semantic color system works in a real design system like Superkube. This will help you see the concepts described above in action.
Additionally, if you haven’t read it yet, check out the article “Creating a Color Palette for Design Systems” to understand how to create color palettes for a design system.
Email me at [email protected] if you have any questions.
My books
Subscribe for news about upcoming
books and my projects
Stay tuned and be the first to know about the release of my new
books and future projects.