Better

Ethan的博客,欢迎访问交流

Themes and Colors in UI design

主题和颜色的选取与规范能看出一个产品的调性,体现专业度的同时,更是可以反向指导设计和开发,减少设计和开发之间的沟通隔阂,提升开发效率。本文就近期使用 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 单位


留言