主题和颜色的选取与规范能看出一个产品的调性,体现专业度的同时,更是可以反向指导设计和开发,减少设计和开发之间的沟通隔阂,提升开发效率。本文就近期使用 tailwindcss 及其相关生态调研如 shadcn/ui、Radix ui 做一些思考总结。
背景
发现一个比较普遍的现象:开发者往往不知道要选择使用那个颜色,而设计者又往往会滥用各种颜色,出现审美冲突不说,更是会对产品造成伤害,比如看起来就感觉很没有层次感、呼吸感或是花里胡哨的山寨风,同时也会对开发体验造成伤害,由于没有规范,其实开发效率是更低的,沟通成本更高,同时为了实现某些 corner case 效果,开发成本也更高,但最终产品表现却很糟糕,属实是竹篮打水一场空。
从最近几年的设计系统发展来看,整个行业处在稳步发展中,从以前的莽荒时期,发展到封闭的组件系统,到组件库支持 token tweaking,再到现在开始强调主题、色阶、原子化等,设计系统的共识会逐渐形成,更是为了将来的自动化奠定了基础,如 AI 自动化生成等。
Colors
Semantic aliases:语义化别名,在 tailwindcss 或是其他解决方案中,都会由提供实际的色阶名称来引用色阶,如 blue、red 等,这可以工作,但会存在一些问题,比如因为过于自由造成颜色的滥用,同时也并不利于开发过程中建立起迅速反应的心智模型,比如我觉得这里应该用主题色,会第一时间想到 primary 或 accent,而不是这个项目是 blue,那个项目是 red,尤其是在实现主题切换时,简直就是灾难,因此我们通常会创建语义化别名,如 accent、primary、neutral、brand、success、warning、danger 等。
除此之外,比如 Radix Colors 中每一个色阶都是为了特定场景设计的,为了帮助您的团队知道要使用哪一色阶,您可以根据设计的用例提供别名。举例如下
:root {
--accent-base: var(--blue-1);
--accent-bg-subtle: var(--blue-2);
--accent-bg: var(--blue-3);
--accent-bg-hover: var(--blue-4);
--accent-bg-active: var(--blue-5);
--accent-line: var(--blue-6);
--accent-border: var(--blue-7);
--accent-border-hover: var(--blue-8);
--accent-solid: var(--blue-9);
--accent-solid-hover: var(--blue-10);
--accent-text: var(--blue-11);
--accent-text-contrast: var(--blue-12);
}
Radix ui
Radix 自定义色板通过定义三个关键颜色:Accent/Gray/Background,Background 主要用于定义整个应用的底色,Accent 和 Gray 会通过算法生成 12 个色阶,可以简单理解为在亮色主题下,1-12 表示由亮到暗,暗色主题则相反,表示由暗到亮。在 Radix 针对每个色阶给了明确的使用场景定义如下
- Background:范围 1-2,用于背景色,通常搭配 11-12 号字色使用
- 1 App background
- 2 Subtle background
- Interactive components:范围 3-5,常用于组件和组件交互性,如 hover 效果
- 3 UI Element background
- 4 hovered ui element background
- 5 active ui element background
- Borders and separators:范围 6-8,常用于边框、分割符、其中 gray-8 还可用于禁用文本
- 6 subtle borders and separator
- 7 ui element border and focus rings
- 8 hovered ui element border
- Solid colors:范围 9-10,常用于纯色背景、按钮,gray 系也可以用于禁用文本
- 9 solid background
- 10 hovered solid background
- Accessible text:范围 11-12,用于字体颜色,其中 12 号用于高对比度,11 号用于次要问题和链接等
- 11 low-contrast text
- 12 high-contract text
Accent color 常用于主按钮、链接和其他交互元素
刚使用 tailwindcss 时,被里面的众多颜色头晕,甚至有些颜色有点傻傻分不清楚,但其实是可以分类的,在 Radix 中对此有着明确的定义。确定你的调色板,你需要如下步骤
- 选择你的品牌色 Accent Color:大部分品牌色是为白色文本设计的,少部分是为黑色文本设计的,也可以自定义品牌色
- 选择你的灰度 Gray Color:提供了一个纯灰色和一些色调的灰色尺度。
- gray 纯灰度、mauve 基于紫色的色调、slate 基于蓝色的色调、saga 基于绿色的色调、olive 基于石灰色调、sand 基于黄色色调
- 如果你想要一个中性的氛围,或者你想要保持简单, gray 可以很好地与任何色调或调色板工作。
- 或者选择与你的强调色调最接近的色调饱和的灰度。差别是微妙的,但这将创造一个更丰富多彩和和谐的氛围。
- 选择你的语义:通过颜色传达语义
- Error:red, ruby, tomato, crimson
- Success:green, teal, jade, grass, mint
- Warning:yellow, amber, orange
- Info:blue, indigo, sky, cyan
- 选择你的文本:11-12 用于表示高低对比度文本,根据你想要的氛围,你可以使用你的重音等级或灰度等级。
tailwindcss
不同于上面 Radix ui,tailwindcss 提供的调色板只有 11 个,应该是生成算法有些许不同。
如果你想生成自己的色板,可以使用在线生成网站 UI Colors
您可以通过在配置文件中导入 tailwindcss/colors 并选择要使用的颜色(如有意限制调色板数量,可以减少不需要的颜色智能提示),同时也可以给颜色起别名,以方便记忆
const colors = require('tailwindcss/colors')
module.exports = {
theme: {
colors: {
transparent: 'transparent',
current: 'currentColor',
black: colors.black,
white: colors.white,
gray: colors.slate,
green: colors.emerald,
purple: colors.violet,
yellow: colors.amber,
pink: colors.fuchsia,
},
},
}
当然也可以通过 extend 方式新增颜色,如品牌色
module.exports = {
theme: {
extend: {
colors: {
brown: {
50: '#fdf8f6',
100: '#f2e8e5',
200: '#eaddd7',
300: '#e0cec7',
400: '#d2bab0',
500: '#bfa094',
600: '#a18072',
700: '#977669',
800: '#846358',
900: '#43302b',
},
}
},
},
}
你同样可以语义化你的颜色,但 tailwind 的建议是如果你正在处理一个需要支持多个主题的项目,那么使用更抽象的名称可能更有意义,否则我们建议对大多数项目坚持默认的命名约定。
const colors = require('tailwindcss/colors')
module.exports = {
theme: {
colors: {
primary: colors.indigo,
secondary: colors.yellow,
neutral: colors.gray,
}
}
}
Themes
shadcn/ui
了解 components.json 配置中两个字段
- baseColor: "gray" | "neutral" | "slate" | "stone" | "zinc",感觉可以理解成上面提到的 Gray Color 的选择
- cssVariables: boolean,表示针对 theming 使用 css variable 还是 Tailwind css utility classes
dark:
shadcn/ui 中默认关于 css variables 一些约定(如需要更多,可以结合 tailwind 自行扩展)
- Default 类:background|foreground 用来定义 body 等之类的
- Primary 类:bg-primary|text-primary-foreground 常用于 Button 等
- Secondary 类:用于二级 Button 等
- Accent 类:用于悬浮效果等强调
- Destructive:表示毁灭操作
- Muted 类:用于定义 TabsList|Skeleton|Switch 等
- Card 类:用于定义 Card
- Popover 类:DropdownMenu|HoverCard|Popover 等
- 其他:border|input|ring|radius|chart
针对 theming 的配置具体
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 224 71.4% 4.1%;
--card: 0 0% 100%;
--card-foreground: 224 71.4% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 224 71.4% 4.1%;
--primary: 262.1 83.3% 57.8%;
--primary-foreground: 210 20% 98%;
--secondary: 220 14.3% 95.9%;
--secondary-foreground: 220.9 39.3% 11%;
--muted: 220 14.3% 95.9%;
--muted-foreground: 220 8.9% 46.1%;
--accent: 220 14.3% 95.9%;
--accent-foreground: 220.9 39.3% 11%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 20% 98%;
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 262.1 83.3% 57.8%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark {
--background: 224 71.4% 4.1%;
--foreground: 210 20% 98%;
--card: 224 71.4% 4.1%;
--card-foreground: 210 20% 98%;
--popover: 224 71.4% 4.1%;
--popover-foreground: 210 20% 98%;
--primary: 263.4 70% 50.4%;
--primary-foreground: 210 20% 98%;
--secondary: 215 27.9% 16.9%;
--secondary-foreground: 210 20% 98%;
--muted: 215 27.9% 16.9%;
--muted-foreground: 217.9 10.6% 64.9%;
--accent: 215 27.9% 16.9%;
--accent-foreground: 210 20% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 20% 98%;
--border: 215 27.9% 16.9%;
--input: 215 27.9% 16.9%;
--ring: 263.4 70% 50.4%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
tailwindcss
tailwindcss 支持 theme 配置定义项目 color palette, type scale, fonts, breakpoints, border radius values 等,关于 colors 的定义你可以这么做
module.exports = {
theme: {
screens: {
sm: '480px',
md: '768px',
lg: '976px',
xl: '1440px',
},
colors: {
'blue': '#1fb6ff',
'purple': '#7e5bef',
'pink': '#ff49db',
'orange': '#ff7849',
'green': '#13ce66',
'yellow': '#ffc82c',
'gray-dark': '#273444',
'gray': {
100: '#f7fafc',
// ...
900: '#1a202c',
},
'gray-light': '#d3dce6',
},
fontFamily: {
sans: ['Graphik', 'sans-serif'],
serif: ['Merriweather', 'serif'],
},
extend: {
spacing: {
'128': '32rem',
'144': '36rem',
},
borderRadius: {
'4xl': '2rem',
}
}
}
}
Ionic
在 Ionic 中定义了 9 中默认颜色,每种颜色实际上是多个属性的集合,包含 shade 和 tint。
- Primary、Secondary、Tertiary
- Success、Warning、Danger
- Dark、Medium、Light
通过设置 ionic 组件的 color 属性以应用指定的颜色,你也可以新增 9 中默认之外的颜色。
如果你需要修改颜色,你需要修改所有相关的变体,以 button 按钮举例,primary 用于背景色,primary-contrast 用于文本颜色,shade 和 tint 则表示按钮的不同状态。
:root {
--ion-color-primary: #0054e9;
--ion-color-primary-rgb: 0,84,233;
// base color 的反面
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-contrast-rgb: 255,255,255;
// 比 base color 更暗一点
--ion-color-primary-shade: #004acd;
// 比 base color 更亮一点
--ion-color-primary-tint: #1a65eb;
// ……
}
关于 Themes 定义,Ionic 主要分为 Application Colors 和 Stepped Colors 两部分,前者可以修改大部分 Ionic 组件的外观,阶梯式颜色用于某些 Ionic 组件的变体。均通过修改 CSS Variable 实现。
- Application Color 有 background-color、text-color、drop-color、overlay-background-color、border-color、box-shadow-color 和一些组件细节部分
- Stepped Color:在探索了不同的自定义 Ionic 主题的方法后,我们发现不能只使用一种背景色或文本色。为了在整个设计中提现重要性和深度,我们需要使用不同色调的背景和文本颜色。于是就有了 Stepped Color,这一点和 tailwindcss 是一致的设计
在手机端通常还有一个常见需求,动态设置字体缩放
- 在 typography.css 中会定义 --ion-dynamic-font 变量,默认值是 16px。
- 进行自定义组件设计时,需要将 px 转为使用 rem 单位