I’ve been thinking a lot about colors recently. It is one of the first things I do when starting a new design project, and one of the things that goes through iterations as the project progresses. Colors express much that is difficult to quantify: information, fashion, mood, and many other intangible qualities that make software feel comfortable or hostile. Color systems can be difficult to work with; the reasoning behind the systems is often opaque, and they are deeply embedded in apps and tools. I’ve been working on a tool for myself called Design Tokens Generator, which I hope will lessen the work of iterating on colors, and increase the satisfaction of doing so.
How I generate colors
I used to start projects by generating a palette at coolors.co and turning it into color scales via an online Tailwind color scale generator. I first saw this with Google’s Material Design, and Tailwind was where I had the most first-hand experience. It was a step up from picking arbitrary colors, but I found the gradations limiting. It felt like painting with a limited set of crayons.
Over the years, my process gained a few new tricks: I started creating different color scales for different uses, breaking away from the 50-950 steps; I started blending colors in different proportions within a single scale; and I started computing colors in OKLCH so a move in lightness reads the same whether the hue is yellow or blue. One of the benefits of varying color scales based on semantic meaning is that we can use colors to build structure instead of relying solely on signposts. We have a natural acuity for visualizing surfaces and depth, and color systems should take full advantage of that.
Sunnyday has a particularly dense UI which benefits from tweaking the nuances of how surfaces layer upon each other. I want to say I did a good job.
The challenge with this approach is that the work is no longer simple. And this leads to two broad problem areas:
First, color systems are rarely one-and-done. We often need to make adjustments to accommodate new uses or change how the product feels. Because there are different algorithms for each scale, and they are all anchored on different colors, and they are often blended on a graphics tool, tweaking one requires going through the entire motion to generate the new hexes. Furthermore, documentation is often an afterthought, and the method for generating the color is stuck in a Figma file from years ago or no longer preserved.
Second, keeping colors in sync between the various tools that I use is a frustratingly menial job. As an illustration, if I need to add a new in-between color, I need to go into Figma to add the color, make any name changes, ensure that one-off colors are updated properly, and then do the same in every software project where the colors are used. The complexity increases exponentially with the sophistication of color generation, and over time I get bogged down in the minutiae.
These little speed bumps make band-aid fixes so much more tempting. New colors get added arbitrarily: why not just pick an arbitrary one and go? Tokens drift because it’s easier to just eyeball it in the codebase. Swapping color references to avoid rejigging the scale. The system that was once coherent turns discordant.
Keeper of yellows
I had a thought the other day: what if the methodology for generating colors could be recorded and reproduced. What if adding a new color would then be as easy as pointing to another point on that continuum. And lastly, what if all changes synced and perpetuated across all usages effortlessly.
These gave rise to the utility dtg. Design Tokens Generator (dtg) is a tool that lets designers express color systems in algorithms, describe how they generate into variables, and sync those variables across the tools and codebases that use them.
Let’s dive into an example. These days, I’m obsessed with color scales that start at a certain color but tint shift into a new tone as they move along a brightness scale. In Sunnyday’s light mode, I have a very light blue surface that gets cooler as it darkens. Inside of dtg, I declare an ink-ramp algorithm named sky with a base of #fcabda and an ink of #012012. I then have a set of semantic variable names that point to various points on the scale, such as sky/15, where the selector (the /15) means 15% ink over that base. The token name is the recipe.
Here’s a snippet from the configuration file:
dtg: 1
modes: [light]
systems:
sky: # a cool ink over a warm base
algorithm: ink-ramp
params: { base: "#fcabda", ink: "#012012" }
core:
surface:
canvas: [sky/0] # the base, with no ink over it
card: [sky/15] # 15% ink over the base
floating: [sky/22]
In the past, I would calculate the colors once, often by creating overlapping squares in Figma, setting the opacity, picking them with the eyedropper, and then setting them into variables. Now, I declare them in a configuration file, generate the CSS file, and check it into the codebase.
The beauty of this system is that if I decide that card needs to be slightly darker, I can nudge it towards sky/16 or sky/18 — or sky/15.5, since the selector is a coordinate on a continuous ramp rather than a position in a list. In fact, I can nudge the entire scale by 1 or 2 points or switch up the base or ink color and maintain the brightness steps. This operation is as easy as setting one number or color in the UI and looking at the updated result. Gone are the days when I had to scavenge for where the original worksheet lived, update all of them, copy everything over, and do it all over again until I got to what I want. The iteration cycle is night and day.
An upside of this approach is that since the color scale is now defined in a continuous spectrum, we can have infinite mappings and different step sizes between the different variables. In Sunnyday alone, I have more than 15 uses of the sky ink-ramp with step sizes ranging from 0.5% to 15% between them. This lets me really fine-tune the relationships between adjacent surfaces, as opposed to leaving them to the mercy of a discrete, fixed-step scale.
Last but not least, because colors are directly materialized into their use sites, it becomes simpler to keep them in sync between all the different tools. As an example, in the past when I had to rejigger a color scale it was a nightmare, because I had to first update the primitives, find every other variable that pointed to it and make sure they were still right, and then do the same in the codebase. Mapping this in dtg generates a more comprehensive list, but because the mappings are semantic, AI can compare the list against all the surfaces and confirm it’s up to date.
I have converted this website and Sunnyday to dtg and can confirm that it works really well. For Sunnyday, it cut the iteration time I described above from days to minutes. For fifthrevision, every theme went from a set of arbitrary, unmanaged colors into a single set generated by a fixed algorithm, which is much easier to maintain. It also means I no longer have to go hunting for the arcane Figma file where it all originated every time I want to make a change.
Bells and whistles
Other features in dtg cover additional things you might want your color system to do. There are ways to generate directly addressable color scales like blue 50-950. Because you name the stops, a palette in steps of 50 or an uneven ramp is just a different map. There are other types of algorithms such as oklch-scale, which blends out from the 500 base in OKLCH toward a light and a dark target.
Another cool feature that falls out of algorithmic generation is the ability to twin opaque and transparent colors. sky/15 on the ink-ramp algorithm materializes a color that has 15% of the ink composited over the base; sky/15/ink generates the ink as a 15% alpha only. This is especially useful for separators and hovers that may have to sit over unknown surfaces. Finally, there is support for overrides and color pinning. These values sit in the definition next to the system they bend, and they survive regeneration.
The rest of dtg is quality-of-life stuff. dtg emit can write any supported variable format, including CSS, SCSS, Tailwind v4, and DTCG. Then there’s a web server, dtg dev, that renders a web app where palettes are displayed in their full glory: the OKLAB delta E between adjacent steps, which roles demanded each step, side-by-side comparisons, color modes, etc. I’m especially excited for this since it really takes color work to the next level. Designers should be able to go in and use all the tools at their disposal to tweak the colors to their heart’s content. It doesn’t write back to the definition yet — that’s the next piece of work — but once it does, the entire lifecycle is complete.
I have a few ideas for immediate next steps: supporting per-project custom algorithms, gradients and more complex color definitions. I’ve also considered non-color tokens like font ramps and corner radii, but I’m going to focus on making the color experience first-rate before covering the rest of the design system.
That’s where 0.2.0 is at the moment. The project is open source and freely available on GitHub. Please use it, tell me what you want dtg to do, and go out and make some kick-ass design systems.