这是用户在 2024-5-20 10:42 为 https://evilmartians.com/chronicles/better-dynamic-themes-in-tailwind-with-oklch-color-magic 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?

Better dynamic themes in Tailwind with OKLCH color magic
借助 OKLCH 色彩魔法,Tailwind 中更好的动态主题

Cover for Better dynamic themes in Tailwind with OKLCH color magic

Topics 主题

Share this post on 分享这篇文章


Translations

If you’re interested in translating or adapting this post, please contact us first.
如果您有兴趣翻译或改编这篇文章,请先联系我们。

Gone are the days of static color palettes! In this article, we’ll delve into the next big thing in web design: dynamic theming with Tailwind. So, if you’re curious about how OKLCH is reshaping the world of Tailwind, you’re in the right place.
静态调色板的时代已经一去不复返了!在本文中,我们将深入研究网页设计中的下一件大事:Tailwind 的动态主题。因此,如果您对 OKLCH 如何重塑 Tailwind 世界感到好奇,那么您来对地方了。

Tailwind isn’t just hype
Tailwind 不仅仅是炒作

It seems like Tailwind has officially blown us away. Sure, it has rustled some jimmies in the community, but even those who were rustled can’t outright deny some of its fantastical perks:
看来 Tailwind 已经正式让我们大吃一惊了。当然,它已经引起了社区中一些人的注意,但即使是那些被打扰的人也不能完全否认它的一些奇妙的好处:

  • Tiny bundle size and high performance: even for huge sites, bundle sizes rarely exceed 10–15KB. This means style sheets are processed in the browser faster than you can say “I love CSS modules more than this”.
    微小的捆绑包大小和高性能:即使对于大型网站,捆绑包大小也很少超过 10-15KB。这意味着样式表在浏览器中的处理速度比您说“我更喜欢 CSS 模块”的速度要快。
  • Built-in code splitting: there’s just one CSS bundle, so no headaches about loading separate styles for components. Styles are delivered hand-in-hand with components, BFF-style.
    内置代码分割:只有一个 CSS 包,因此无需为组件加载单独的样式而烦恼。样式与组件一起交付,BFF 风格。
  • Markup and style coupling: classes are baked right into the markup, boosting readability and portability. Moving a component from one project or an example to another? Piece of cake.
    标记和样式耦合:类直接嵌入到标记中,从而提高了可读性和可移植性。将组件从一个项目或示例移动到另一个项目或示例?小菜一碟。

Now, one thing we’re aiming to hype up in particular today is the color management system. While Tailwind provides a fabulous palette out of the box, if you’re dreaming of dynamic theme generation at runtime, static classes are just not going to work.
现在,我们今天要特别强调的一件事是色彩管理系统。虽然 Tailwind 提供了一个很棒的开箱即用的调色板,但如果您梦想在运行时生成动态主题,静态类是行不通的。

…but stick around, and we’ll give you the full lowdown on how to make that happen.
…但是请留下来,我们将为您提供有关如何实现这一目标的完整内幕。

What are dynamic themes, and why do we even need them?
什么是动态主题,为什么我们需要它们?

A dynamic theme lets users play Van Gogh and choose the core interface colors. The medium that allows users to make their selection can vary—it could be a fixed color set or a fancy UI “color slider.” But the endgame is the same: users can jazz up their product to match their taste or needs with their fave shade—brightening their day, every day.
动态主题让用户可以扮演梵高并选择核心界面颜色。允许用户做出选择的媒介可能会有所不同——它可以是固定的颜色集,也可以是精美的 UI“颜色滑块”。但最终的结果是一样的:用户可以用他们最喜欢的颜色来让他们的产品更加有趣,以符合他们的品味或需求,让他们的每一天都充满阳光。

Note how the entire product color scheme changes in real-time as you adjust the hue picker.
请注意当您调整色调选择器时整个产品配色方案如何实时变化。

Beyond global themes, the magic of dynamic coloring can also be applied to very specific UI components. Let’s imagine a promo banner that automatically selects its accent color based on the image it’s displaying. Now you’ve got a banner that’s not only eye-catching—it’s color-coordinated, down to the pixel.
除了全局主题之外,动态着色的魔力还可以应用于非常具体的 UI 组件。让我们想象一个促销横幅,它根据所显示的图像自动选择其强调色。现在您已经有了一个横幅,它不仅引人注目,而且颜色协调,精确到像素。

Or consider a social network where users can choose their profile’s dominant color, which then affects how it appears to visitors. Now you’re curating the visual experience for anyone who lands on your slice of the social web.
或者考虑一个社交网络,用户可以选择其个人资料的主色,然后影响其对访问者的显示方式。现在,您正在为所有登陆您社交网络的人打造视觉体验。

Watch me redecorate this promo banner. From dusk beach till dawn. Demo by my colleague Anton Lovchikov.
看我重新装饰这个促销横幅。从黄昏海滩到黎明。我的同事 Anton Lovchikov 进行的演示。

To be clear, this is not just an aesthetic gimmick. By personalizing a product, users bond with it. Consider it an added layer of engagement, which could lead to better retention and overall satisfaction. The choice of color scheme is no minor detail, it’s the wellspring that every interface’s overall vibe pours out from.
需要明确的是,这不仅仅是一个审美噱头。通过个性化产品,用户可以与它建立联系。将其视为额外的参与层,这可能会带来更好的保留率和整体满意度。配色方案的选择不是一个小细节,它是每个界面整体氛围的源泉。

That being said, “dynamic” doesn’t mean every single color in an interface should be customizable. After all, some colors send signals to the user and follow time-tested patterns. These include things like errors, which need to sort of “scream” in red, “warnings” that wave a yellow flag, and success notices that give a green “thumbs up”.
话虽这么说,“动态”并不意味着界面中的每种颜色都应该是可定制的。毕竟,有些颜色会向用户发送信号并遵循经过时间考验的模式。其中包括错误(需要用红色“尖叫”)、挥舞黄色旗帜的“警告”以及绿色“竖起大拇指”的成功通知。

Why dynamic themes are a rarity-and how this is changing
为什么动态主题很少见——以及这种情况正在发生怎样的变化

So, what is new here, exactly? You might be thinking, “We have HSL, right? Hue, Saturation, Lightness! Give users a hue slider, pop that value into a CSS variable, and bam, you’re golden.” But why isn’t everyone on that bandwagon?
那么,这里到底有什么新内容呢?你可能会想,“我们有 HSL,对吧?色相、饱和度、明度!给用户一个色调滑块,将该值弹出到 CSS 变量中,然后砰的一声,你就成功了。”但为什么不是每个人都跟上这个潮流呢?

The culprit is color perception. The RGB color space isn’t the best when it comes to delivering uniform perception, leading to a mishmash of visual outcomes. In other words: shifting hue with consistent saturation and lightness can lead to wildly different results.
罪魁祸首是色彩感知。 RGB 色彩空间在提供统一的感知方面并不是最好的,从而导致视觉结果的混乱。换句话说:在饱和度和亮度一致的情况下改变色调可能会导致截然不同的结果。

There are 4 buttons. The first column has two buttons and represents the HSL space before and after using the rotation angle, and the second column with the other two buttons represents the OKLCH space before and after using the rotation angle. After changes in HSL, the contrast between the background and text is lower, unlike OKLCH.

In HSL, hue changes could lead to accessibility issues due to low contrast
在 HSL 中,色调变化可能会因对比度低而导致可访问性问题

This brings us to the core challenge of designing and implementing dynamic themes: teams often have to invent their own color math to ensure some level of perceptual consistency. (Imagine a developer having to create cryptography algorithms every time they made a new project. Why do that? There are already battle-tested libraries for that, like OpenSSL.)
这给我们带来了设计和实现动态主题的核心挑战:团队通常必须发明自己的颜色数学,以确保一定程度的感知一致性。 (想象一下,开发人员每次创建新项目时都必须创建加密算法。为什么要这样做?已经有经过实战检验的库,例如 OpenSSL。)

This is where OKLCH comes into play. It’s not just another color space; it’s specifically designed for uniform color perception. With OKLCH, you can slide the hue around and achieve consistent visual results—with no more need for any DIY formulas.
这就是 OKLCH 发挥作用的地方。它不仅仅是另一个色彩空间;它也是一个色彩空间。它专为统一色彩感知而设计。借助 OKLCH,您可以滑动色调并获得一致的视觉效果,而不再需要任何 DIY 公式。

Next, we’ll break down how to seamlessly integrate OKLCH with Tailwind for a robust dynamic theme.
接下来,我们将详细介绍如何将 OKLCH 与 Tailwind 无缝集成以获得强大的动态主题。

Replacing semantic classes with spectrum-based ones
用基于频谱的语义类替换语义类

Heads up: there’s a full “product” demo at the end of this article, showcasing all the juicy details we’ll cover here.
注意:本文末尾有一个完整的“产品”演示,展示了我们将在此处介绍的所有有趣的细节。

The first step towards integration is switching from spectrum-based classes like text-red-500 to semantic classes such as text-accent-500 or bg-secondary-300. This will be part of your pact with the designer: any color we want to spice up at runtime needs to be semantic.
集成的第一步是从基于频谱的类(如 text-red-500 )切换到语义类(如 text-accent-500bg-secondary-300 )。这将是您与设计师达成的协议的一部分:我们想要在运行时增添色彩的任何颜色都需要是语义化的。

There’s only one exception: signal classes, like errors or successes. Although, I’d still nudge you towards semantic classes, especially since linking the Tailwind spectrum palette with a semantic token is pretty straightforward.
只有一种例外:信号类,例如错误或成功。尽管如此,我仍然会推动您走向语义类,特别是因为将 Tailwind 频谱调色板与语义标记链接起来非常简单。

You can have as many dynamic colors as your (product’s) heart desires; you can let users pick one or more, but typically complementary colors should be distinct from the main shade. Sadly, with dynamic themes, we’re unable to set them in stone beforehand. But… you can still generate them at runtime!
您可以根据您(产品)的喜好拥有任意多种动态颜色;您可以让用户选择一种或多种,​​但通常互补色应与主色调不同。遗憾的是,对于动态主题,我们无法事先将它们固定下来。但是......您仍然可以在运行时生成它们!

If we were in the RGB realm, we’d inherit the same old problem: generated complementary colors in perception would differ massively. But lucky for us, we’re in OKLCH world. Complementary colors can be whipped up on the fly by tweaking the hue using a fancy algorithm. (For instance, for 4 distinctly different colors, you’d add 360/4=90 to the base hue.)
如果我们处于 RGB 领域,我们将继承同样的老问题:生成的互补色在感知上会存在巨大差异。但幸运的是,我们身处 OKLCH 世界。通过使用奇特的算法调整色调,可以即时调出互补色。 (例如,对于 4 种截然不同的颜色,您可以将 360/4=90 添加到基本色调中。)

Crafting classes with dynamic colors
具有动态色彩的手工课程

Once you’ve settled on your semantic names (like accent, secondary, and so on), it’s time to roll out some new classes in the Tailwind config. For a shade of 300, the raw value in the tailwind.config.js should look something like this:
一旦您确定了语义名称(例如 accentsecondary 等),就可以在 Tailwind 配置中推出一些新类了。对于 300 的阴影, tailwind.config.js 中的原始值应如下所示:

oklch(var(--dn-accent-300, 0.762 0.177 190.000) / <alpha-value>)

Let’s break that down:
让我们分解一下:

  • oklch. This color space is already supported by most browsers, and it also lets you dabble with the vibrant P3 colors. If you’re fretting about older browser support, you can play it safe with rgb.
    oklch 。大多数浏览器已经支持此色彩空间,并且它还可以让您涉足充满活力的 P3 颜色。如果您担心旧版浏览器的支持,可以使用 rgb 来保证安全。
  • --dn-accent-300. Here, 300 is our shade; accent is the semantic name, and dn is shorthand for “dynamic”. We sprinkle that in to avoid any potential variable clashes.
    --dn-accent-300 。在这里, 300 是我们的阴影; accent 是语义名称, dn 是“动态”的简写。我们将其加入以避免任何潜在的变量冲突。
  • 0.762 0.177 190.000. These are your fallback values for the CSS variable. Without these, if there’s no value up the cascade, the color will just be black all around; I always add them for extra safety. But hang tight! We’ll discuss the FOUC issue later in this article, ensuring that our needed variables are always present.
    0.762 0.177 190.000 。这些是 CSS 变量的后备值。如果没有这些,如果级联上没有值,那么周围的颜色将只是黑色;为了额外的安全,我总是添加它们。但请坚持住!我们将在本文后面讨论 FOUC 问题,以确保我们所需的变量始终存在。
  • <alpha-value>. This bit is for Tailwind, and lets it handle color opacity syntax, like bg-accent-300/50, for a 50% transparent background.
    <alpha-value> 。该位用于 Tailwind,并让它处理颜色不透明度语法,例如 bg-accent-300/50 ,用于 50% 透明背景。

Creating the colors: going full brightness
创建颜色:全亮度

I casually dropped an accent color example earlier, 0.762 0.177 190.000. But you might be wondering, “Where did that come from?” Well, it’s time for some color math magic. And thanks to OKLCH, we don’t have to reinvent this wheel.
我之前随意删除了一个强调色示例, 0.762 0.177 190.000 。但你可能会想,“它是从哪里来的?”好吧,是时候施展一些色彩数学魔法了。感谢 OKLCH,我们不必重新发明这个轮子。

Now, the Hue component will either be set when creating classes (via fallback values) or user-picked. That’s 1/3 of the work done.
现在,色调组件将在创建类时设置(通过后备值)或由用户选择。这是已完成工作的 1/3。

In true Tailwind style, we’ve got 11 shades, ranging from 50 to 950. In OKLCH terms, these shades differ in Lightness, which ranges from 0% to 100%. We’ll manually match Lightness to the Tailwind palette, so that our custom-made dynamic theme adheres to the look and feel of the stock palette. One may think you can find a formula to find the Lightness with mathematical precision, but nothing beats the trained eye of a designer:
在真正的 Tailwind 风格中,我们有 11 种色调,范围从 50 到 950。在 OKLCH 术语中,这些色调的亮度不同,范围从 0% 到 100%。我们将手动将亮度与 Tailwind 调色板相匹配,以便我们定制的动态主题符合库存调色板的外观和感觉。人们可能认为您可以找到一个公式来以数学精度找到亮度,但没有什么比设计师训练有素的眼睛更好的了:

const lightness = [
  97.78, 93.56, 88.11, 82.67, 74.22, 64.78, 57.33, 46.89, 39.44, 32, 23.78,
];

So, our job boils down to calculating Chroma.
因此,我们的工作归结为计算色度。

There are numerous ways to do this, and I’ll shed light on two: max chroma (which comes with visual inconsistency), and the less radiant, but extremely consistent one.
有很多种方法可以做到这一点,我将介绍两种:最大色度(会带来视觉不一致),以及辐射度较低但极其一致的一种。

Let’s start with the max chroma.
让我们从最大色度开始。

We’ll automate auto-magic this! We’ll create a color with the existing Lightness and Hue. Then, for the starting Chroma, we’ll aim way beyond the available color space—say, 0.4. From there, the OKLCH converter will grab this off-the-chart Chroma and compute the max possible value.
我们将自动化自动魔法!我们将使用现有的亮度和色相创建一种颜色。然后,对于起始色度,我们的目标将远远超出可用的色彩空间,例如 0.4 。从那里,OKLCH 转换器将获取该图表之外的色度并计算最大可能值。

This approach will give you fairly consistent visual results, all thanks to the OKLCH math. But brace yourself, some inconsistencies in Chroma might rain on your parade.
这种方法将为您提供相当一致的视觉结果,这一切都归功于 OKLCH 数学。但请做好准备,Chroma 中的一些不一致可能会影响您的游行。

3 different color palettes with squares colored in shades from 50 to 950.

On one hand, this method brings out the most vibrant colors. On the other, the brightest shade varies with the Hue—sometimes it might be shade 200, other times 500.
一方面,这种方法可以呈现出最鲜艳的色彩。另一方面,最亮的色调随色调而变化,有时可能是 200,有时是 500。

I would go this route (that is, going full brightness) if, for some reason, I needed dynamic coloring on landing pages—this is where P3 shines the most, and users are blown away by extremely vibrant colors.
如果出于某种原因,我需要在登陆页面上进行动态着色,我会走这条路(即全亮度)——这是 P3 最耀眼的地方,用户会被极其鲜艳的颜色所震撼。

But hold on to your hats—there’s another way.
但请坚持住——还有另一种方法。

Creating colors with optimal consistency
创建具有最佳一致性的颜色

To ensure maximum visual consistency, we’ll need to manually set the Chroma for each shade. And here’s how the magic happens (spoiler alert: no magic at all):
为了确保最大的视觉一致性,我们需要手动设置每种色调的色度。魔法是这样发生的(剧透警告:根本没有魔法):

Head on over to oklch.com, set the desired Lightness, and slide the Chroma slider upwards until you hit a sweet spot where there are no gaps in the Hue spectrum. Gaps mean that there won’t be such a color for this Hue, meaning the color will fallback to closest value, and here’s where you’ll have inconsistency once again. Once you find a Chroma value that has no gaps in the spectrum, save it.
前往 oklch.com,设置所需的亮度,然后向上滑动色度滑块,直到达到色调光谱中没有间隙的最佳位置。间隙意味着该色调不会有这样的颜色,这意味着颜色将回退到最接近的值,这就是您将再次遇到不一致的地方。一旦找到光谱中没有间隙的色度值,请将其保存。

As a result, we get a pre-defined array, just like with Lightness, and this time the palette is very consistent.
结果,我们得到了一个预定义的数组,就像 Lightness 一样,而且这次调色板非常一致。

const chroma = [
  0.0108, 0.0321, 0.0609, 0.0908, 0.1398, 0.1472, 0.1299, 0.1067, 0.0898,
  0.0726, 0.054,
];
3 different color palettes with squares colored in shades from 50 to 950.

Very consistent, even with very different Hue components.
即使色调成分非常不同,也非常一致。

The coolest part is that if you compare this dynamic palette with the static one from Tailwind, you’ll see very little difference; your dynamic one will play nicely with those signal spectral classes.
最酷的部分是,如果您将这个动态调色板与 Tailwind 的静态调色板进行比较,您会发现几乎没有什么区别;您的动态信号将与这些信号频谱类别很好地配合。

Feel free to change play with both algorithms in this demo!
请随意改变本演示中两种算法的玩法!

Now, that dynamic colors and Tailwind are in sync, over the next few sections, we’ll turn our attention to creating the “ultimate” demo. Let’s get going.
现在,动态颜色和 Tailwind 已同步,在接下来的几个部分中,我们将把注意力转向创建“终极”演示。我们走吧。

Crafting the ultimate demo
制作终极演示

Let’s put this theory to test in a live interface and delve deep into the quirks and intricacies of the task at hand.
让我们在实时界面中测试这个理论,并深入研究手头任务的怪癖和复杂性。

A picture of a typical SaaS application with a left navigation menu and settings page opened.

By all means, give this UI a try, it’s marvelous! Source code is available on GitHub.
无论如何,尝试一下这个 UI,太棒了!源代码可在 GitHub 上获取。

When to load the runtime
何时加载运行时

The code that calculates those colors relies on the culori library; without it, we’d be stumbling in the dark. On its own, it size is 30+ KB minified and gzipped, which is overkill—like a sports car sitting in a traffic jam. Here are some ways to deal with this:
计算这些颜色的代码依赖于 culori 库;没有它,我们就会在黑暗中跌跌撞撞。就其本身而言,经过压缩和压缩后,它的大小为 30+ KB,这有点过分了——就像一辆陷入交通堵塞的跑车。以下是一些处理此问题的方法:

  • Asynchronous imports: load the library and runtime only when needed using async import(). This is ideal if color customization is reserved for settings pages.
    异步导入:仅在需要时使用 async import() 加载库和运行时。如果为设置页面保留颜色自定义,则这是理想的选择。
  • Serverless: deploy this function in a serverless environment (manually, or using meta-framework APIs like Next, Nuxt, SvelteKit, and so on), and fetch it as users slide through the Hue picker. We end up with a slightly compromised UX, but this works wonders even on low-end devices: small bundles, server-side computations.
    无服务器:在无服务器环境中部署此功能(手动或使用 Next、Nuxt、SvelteKit 等元框架 API),并在用户滑过 Hue 选择器时获取它。我们最终得到了稍微妥协的用户体验,但这即使在低端设备上也能产生奇迹:小捆绑、服务器端计算。
  • Precompilation: While Hue values range from 0 to 360 with floating points, in reality, you could achieve ample customization by setting steps of, say, 5 or 10 for the Hue picker. This gives you a finite number of color combinations: from 36 to 72. Precompute during build, stash it in a CDN, and fetch the data as users play with the slider. 36 combos for one color? That’s a 5KB gzip’d JSON, small enough to ship to the user.
    预编译:虽然色调值的范围为 0 到 360(带有浮点),但实际上,您可以通过为色调选择器设置步长(例如 5 或 10)来实现充分的自定义。这为您提供了有限数量的颜色组合:从 36 到 72。在构建过程中进行预计算,将其存储在 CDN 中,并在用户使用滑块时获取数据。一种颜色有 36 个组合?这是一个 5KB gzip 压缩的 JSON,小到足以发送给用户。

Also, you can always fallback to adding it all in the bundle. If you’re looking for offline capabilities, or you just expect the incoming colors to be very different and changed quite often, then you don’t have another choice.
此外,您始终可以将其全部添加到捆绑包中。如果您正在寻找离线功能,或者您只是希望传入的颜色非常不同并且经常变化,那么您别无选择。

Dealing with FOUC and retaining user choices
处理 FOUC 并保留用户选择

The term FOUC (flash of unstyled content) is usually about a situation where a page is painted before the CSS is fully loaded. In our case we could also have a situation where a browser draws a page before the user-selected theme CSS variables are applied, meaning that users will see our fallback values. So we’ll need to store and retrieve the color somehow.
术语 FOUC(无样式内容的闪存)通常是指在 CSS 完全加载之前绘制页面的情况。在我们的例子中,我们还可能遇到浏览器在应用用户选择的主题 CSS 变量之前绘制页面的情况,这意味着用户将看到我们的后备值。所以我们需要以某种方式存储和检索颜色。

Where to store user’s color choice?
在哪里存储用户的颜色选择?

  • In the browser, for instance, we can do this using Local Storage.
    例如,在浏览器中,我们可以使用本地存储来做到这一点。
  • On the server in a database. This brings in an added spice: the theme gets preserved across devices, and migration is easier (e.g., jumping from two colors to three or tweaking the palette algorithm). But, of course, with great power comes increased complexity.
    在服务器上的数据库中。这带来了额外的乐趣:主题可以跨设备保留,并且迁移更容易(例如,从两种颜色跳到三种颜色或调整调色板算法)。但是,当然,强大的能力也会带来复杂性的增加。
  • Cookies! They do have a size limit, but the great thing is they’re accessible both server-side and client-side. This is perfect if you’re avoiding user profiles and don’t want to pull color libraries into the client.
    饼干!它们确实有大小限制,但最棒的是它们可以在服务器端和客户端访问。如果您要避免用户配置文件并且不想将颜色库拉入客户端,那么这是完美的选择。

Most times, I’d put my money on the database, it’s a much better UX overall.
大多数时候,我会把钱花在数据库上,总体来说这是一个更好的用户体验。

How to store the choice?
如何存储选择?

  • Just save the chosen hue. It’s compact, compatible with any storage, but may face challenges if rendering outside the Node.js ecosystem due to the potential lack of color libraries.
    只需保存所选的色调即可。它结构紧凑,与任何存储兼容,但如果在 Node.js 生态系统之外进行渲染,由于可能缺乏颜色库,可能会面临挑战。
  • Save the entire generated palette; but there’s the catch: it might not play well with cookies.
    保存整个生成的调色板;但有一个问题:它可能不太适合饼干。

For most simple endeavors, a hue should suffice.
对于大多数简单的尝试来说,色调就足够了。

How to retrieve it? 如何找回?

  • If you’re rendering templates server-side (SSR), the easiest method is to fetch the color from cookies or the database and pin the variables as inline styles. Local Storage, unfortunately, won’t make the cut here.
    如果您在服务器端 (SSR) 渲染模板,最简单的方法是从 cookie 或数据库中获取颜色,并将变量固定为内联样式。不幸的是,本地存储不适合这里。
  • Otherwise, you absolutely must save the palette in a client-accessible storage (Local Storage or cookies), and as early as possible (among the very first script tags), retrieve these colors and set the CSS variable values.
    否则,您绝对必须将调色板保存在客户端可访问的存储(本地存储或 cookies)中,并尽早(在第一个脚本标记中)检索这些颜色并设置 CSS 变量值。

For the demo I showed, we brewed our project with Next (employing React Server Components), storing the hue in cookies and sprinkling the variables in inline styles in the root layout component. We also use a serverless function that’s called during initial render, and which gets revalidated every time you change the selected Hue.
对于我展示的演示,我们使用 Next 构建了我们的项目(使用 React Server 组件),将色调存储在 cookie 中,并将变量以内联样式撒在根布局组件中。我们还使用在初始渲染期间调用的无服务器函数,并且每次更改所选色调时都会重新验证该函数。

What to do with Dark Mode?
深色模式该怎么办?

Dealing with Dark Mode is an easy one: we treat dark mode the same way in dynamic themes as in static. Think of your semantic classes as spectral cousins; behind every numbered class hides a specific color. Before, you’d do text-sky-700 dark:text-sky-200. Now? It’s all about text-accent-700 dark:text-accent-200.
处理深色模式很简单:我们在动态主题中以与静态主题中相同的方式对待深色模式。将你的语义类视为光谱表兄弟;每个编号类别的后面都隐藏着特定的颜色。以前,你会做 text-sky-700 dark:text-sky-200 。现在?这都是关于 text-accent-700 dark:text-accent-200 的。

Typically, you’d want to have three states: “default” (from media queries), “force light,” and “force dark.” For simplicity, this demo only has the latter two.
通常,您需要三种状态:“默认”(来自媒体查询)、“强制亮”和“强制暗”。为简单起见,本演示仅包含后两者。

How to display the Hue Picker UI?
如何显示色调选择器 UI?

The regular old <input type="color" /> or non-native pickers just won’t do. They allow picking all color components together, which might puzzle the user. We just want to serve up Hue. The straightforward approach is an <input type="range" /> with a numeric value. But, between us, I doubt users would want such a bare-bones UI.
常规的旧 <input type="color" /> 或非本地选择器是行不通的。它们允许将所有颜色分量一起选取,这可能会让用户感到困惑。我们只是想为顺化提供服务。最简单的方法是带有数值的 <input type="range" /> 。但是,我怀疑用户会想要这样一个简单的用户界面。

For our demo, I made a nifty little cross-framework component: simple-hue-picker.
对于我们的演示,我制作了一个漂亮的小跨框架组件:simple-hue-picker。

The simplest color picker in the universe, 1 kilobyte, cross-framework. As good as it gets!
宇宙中最简单的颜色选择器,1 KB,跨框架。要多好能有多好!

While it isn’t the sharpest tool in the shed, it gives users a decent reference. Plus, it’s lightweight compared to most color-picking libraries and can easily be used in all frameworks and even without one.
虽然它不是棚屋中最锋利的工具,但它为用户提供了不错的参考。另外,与大多数颜色选择库相比,它是轻量级的,并且可以轻松地在所有框架中使用,甚至可以在没有框架的情况下使用。

How to collaborate effectively with designers?
如何与设计师有效协作?

Time for the grand finale question. Dynamic colors are all fun and games until a designer needs a playground. The fix? Just export colors with your favorite hue once (I’d lean into green with hue 150) and serve it in JSON format.
到了最后的问题了。动态色彩总是充满乐趣和游戏,直到设计师需要一个游乐场。修复?只需导出一次您最喜欢的色调的颜色(我倾向于色调为 150 的绿色)并以 JSON 格式提供。

Most design tools can gobble up these color tokens. Mockups are now designed with that one color in mind, but you and your designer will be bound by the same math formula! That’s why they can safely use whatever shade they have, and you can sleep peacefully knowing that no angry user will come to you saying that when they pick yellow, the text suddenly becomes unreadable.
大多数设计工具都可以吞噬这些颜色标记。现在设计模型时会考虑到一种颜色,但您和您的设计师将受到相同的数学公式的约束!这就是为什么他们可以安全地使用他们拥有的任何颜色,并且您可以安心地睡觉,因为知道没有愤怒的用户会来找您说,当他们选择黄色时,文本会突然变得难以阅读。

Wrapping up this colorful journey
结束这趟多彩的旅程

Well, folks, that’s a wrap on our technicolor adventure through the vibrant world of dynamic theming with Tailwind and OKLCH.
好了,各位,我们与 Tailwind 和 OKLCH 一起穿越充满活力的动态主题世界的彩色冒险就结束了。

As you dive back into your projects, remember: colors have the power to evoke emotions, set moods, and shape perceptions. So, why settle for the same old palette when the world is much brighter? Here’s to painting the digital canvas with hues that resonate and themes that truly pop. Happy coding and colorful designing! 🎨🚀
当您重新投入项目时,请记住:颜色具有唤起情感、设定情绪和塑造感知的能力。那么,当世界变得更加明亮时,为什么还要选择同样的旧调色板呢?这是在数字画布上绘制具有共鸣的色调和真正流行的主题。快乐的编码和丰富多彩的设计! 🎨🚀

A big shoutout to my colleague Anton Lovchikov for his invaluable insights on OKLCH color spaces. His expertise helped elevate this article to a whole new level of clarity and depth.
强烈感谢我的同事 Anton Lovchikov 对 OKLCH 色彩空间的宝贵见解。他的专业知识帮助将本文的清晰度和深度提升到了一个全新的水平。

At Evil Martians, we transform growth-stage startups into unicorns, build developer tools, and create open source products. If you’re ready to engage warp drive, give us a shout!
在 Evil Martians,我们将成长阶段的初创公司转变为独角兽,构建开发人员工具并创建开源产品。如果您准备好使用曲速引擎,请告诉我们!

Solve your problems with 1-1 guidance
1-1指导解决您的问题

Are you fighting with the challenges of improving performance, scaling, product shipping, UI design, or cost-effective deployment? Our experts in developer-first startups can offer free tailored recommendations for you—and our engineering team to implement the bespoke strategy.
您是否正在应对提高性能、扩展、产品运输、UI 设计或经济高效部署方面的挑战?我们的开发人员优先初创公司专家可以为您和我们的工程团队提供免费的定制建议,以实施定制策略。

Launch with Martians

In the same orbit 在同一轨道上

How can we help you?

instead
Martians at a glance
17
years in business

We transform growth-stage startups into unicorns, build developer tools, and create open source products.

If you prefer email, write to us at surrender@evilmartians.com