品牌指南的技术实现:从设计稿到代码的系统化落地
品牌指南文档写得再漂亮,如果开发时无法精准还原,也只是一纸空文。本文将讲解如何将品牌规范转化为代码,让每一行 CSS 都散发着品牌气息。
品牌指南的技术视角
品牌规范包含什么
从技术实现角度,品牌指南可以拆解为:
| 类别 | 设计元素 | 技术实现 |
|---|---|---|
| 色彩 | 主色、辅色、中性色 | CSS 变量、设计令牌 |
| 字体 | 字族、字重、字号 | @font-face、排版系统 |
| 间距 | 边距、内边距、行高 | 间距尺度、布局网格 |
| 圆角 | 按钮、卡片、输入框 | 圆角令牌 |
| 阴影 | 层级阴影、悬浮效果 | 阴影令牌 |
| 图标 | 风格、尺寸、颜色 | 图标库、SVG 系统 |
| Logo | 尺寸、间距、变体 | 资源管理、组件封装 |
| 动效 | 时长、缓动、过渡 | 动画令牌 |
设计令牌系统
什么是设计令牌
设计令牌是品牌规范的最小单位——一个命名的值:
{
"color": {
"brand": {
"primary": { "value": "#0066FF" },
"secondary": { "value": "#FF6B35" }
},
"neutral": {
"100": { "value": "#FFFFFF" },
"900": { "value": "#1A1A1A" }
}
},
"spacing": {
"xs": { "value": "4px" },
"sm": { "value": "8px" },
"md": { "value": "16px" }
}
}
令牌分层架构
全局令牌 (Global Tokens)
↓ 基础颜色、字体、间距
语义令牌 (Semantic Tokens)
↓ 背景色、文字色、边框色
组件令牌 (Component Tokens)
→ 按钮背景、输入框边框
/* 1. 全局令牌 - 品牌原始值 */
:root {
--color-blue-500: #0066FF;
--color-blue-600: #0052CC;
--color-gray-100: #F5F5F5;
--color-gray-900: #1A1A1A;
}
/* 2. 语义令牌 - 场景含义 */
:root {
--color-bg-primary: var(--color-gray-100);
--color-text-primary: var(--color-gray-900);
--color-brand-primary: var(--color-blue-500);
--color-brand-hover: var(--color-blue-600);
}
/* 3. 组件令牌 - 具体应用 */
:root {
--button-bg: var(--color-brand-primary);
--button-bg-hover: var(--color-brand-hover);
--button-text: white;
}
Style Dictionary 自动化
使用 Style Dictionary 管理多平台令牌:
// config.json
{
"source": ["tokens/**/*.json"],
"platforms": {
"css": {
"transformGroup": "css",
"buildPath": "build/css/",
"files": [{
"destination": "variables.css",
"format": "css/variables"
}]
},
"scss": {
"transformGroup": "scss",
"buildPath": "build/scss/",
"files": [{
"destination": "_variables.scss",
"format": "scss/variables"
}]
},
"js": {
"transformGroup": "js",
"buildPath": "build/js/",
"files": [{
"destination": "tokens.js",
"format": "javascript/es6"
}]
}
}
}
# 生成所有平台的令牌文件
npx style-dictionary build
色彩系统实现
品牌色彩定义
:root {
/* 品牌主色 */
--brand-primary: #0066FF;
--brand-primary-light: #3385FF;
--brand-primary-dark: #0052CC;
/* 品牌辅色 */
--brand-secondary: #FF6B35;
--brand-accent: #00D4AA;
/* 中性色阶 */
--neutral-50: #FAFAFA;
--neutral-100: #F5F5F5;
--neutral-200: #EEEEEE;
--neutral-300: #E0E0E0;
--neutral-400: #BDBDBD;
--neutral-500: #9E9E9E;
--neutral-600: #757575;
--neutral-700: #616161;
--neutral-800: #424242;
--neutral-900: #212121;
/* 功能色 */
--success: #22C55E;
--warning: #F59E0B;
--error: #EF4444;
--info: #3B82F6;
}
语义化颜色映射
:root {
/* 文本颜色 */
--text-primary: var(--neutral-900);
--text-secondary: var(--neutral-600);
--text-disabled: var(--neutral-400);
--text-inverse: var(--neutral-50);
/* 背景颜色 */
--bg-primary: var(--neutral-50);
--bg-secondary: var(--neutral-100);
--bg-elevated: white;
/* 边框颜色 */
--border-default: var(--neutral-200);
--border-strong: var(--neutral-300);
/* 交互颜色 */
--interactive-primary: var(--brand-primary);
--interactive-hover: var(--brand-primary-dark);
--interactive-active: var(--brand-primary-dark);
}
暗色模式支持
/* 暗色主题令牌 */
[data-theme="dark"] {
--text-primary: var(--neutral-100);
--text-secondary: var(--neutral-400);
--bg-primary: var(--neutral-900);
--bg-secondary: var(--neutral-800);
--bg-elevated: var(--neutral-800);
--border-default: var(--neutral-700);
}
/* 跟随系统 */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--text-primary: var(--neutral-100);
/* ... */
}
}
排版系统
字体家族定义
:root {
/* 品牌字体 */
--font-display: 'Brand Display', 'Noto Sans SC', sans-serif;
--font-body: 'Inter', 'PingFang SC', sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
}
/* 字体加载 */
@font-face {
font-family: 'Brand Display';
src: url('/fonts/brand-display.woff2') format('woff2');
font-weight: 700;
font-display: swap;
}
字号尺度
:root {
/* 模块化尺度 (1.25 比例) */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.25rem; /* 20px */
--text-xl: 1.5rem; /* 24px */
--text-2xl: 2rem; /* 32px */
--text-3xl: 2.5rem; /* 40px */
--text-4xl: 3rem; /* 48px */
/* 行高 */
--leading-tight: 1.25;
--leading-normal: 1.5;
--leading-relaxed: 1.75;
/* 字重 */
--font-normal: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;
}
排版预设
/* 标题样式 */
.heading-1 {
font-family: var(--font-display);
font-size: var(--text-4xl);
font-weight: var(--font-bold);
line-height: var(--leading-tight);
letter-spacing: -0.02em;
}
.heading-2 {
font-family: var(--font-display);
font-size: var(--text-3xl);
font-weight: var(--font-bold);
line-height: var(--leading-tight);
}
/* 正文样式 */
.body-lg {
font-family: var(--font-body);
font-size: var(--text-lg);
line-height: var(--leading-relaxed);
}
.body-base {
font-family: var(--font-body);
font-size: var(--text-base);
line-height: var(--leading-normal);
}
Logo 组件封装
Logo 组件
<template>
<component
:is="href ? 'a' : 'span'"
:href="href"
class="brand-logo"
:class="[`size-${size}`, { 'logo-mono': mono }]"
>
<!-- 完整 Logo -->
<template v-if="variant === 'full'">
<img
:src="mono ? logoMonoSrc : logoSrc"
:alt="alt"
class="logo-image"
>
</template>
<!-- 图标 Logo -->
<template v-else-if="variant === 'icon'">
<img
:src="iconSrc"
:alt="alt"
class="logo-icon"
>
</template>
<!-- 文字 Logo -->
<template v-else-if="variant === 'wordmark'">
<span class="logo-wordmark">{{ brandName }}</span>
</template>
</component>
</template>
<script setup>
defineProps({
variant: {
type: String,
default: 'full',
validator: v => ['full', 'icon', 'wordmark'].includes(v)
},
size: {
type: String,
default: 'md',
validator: v => ['sm', 'md', 'lg'].includes(v)
},
mono: Boolean,
href: String,
alt: {
type: String,
default: 'Brand Logo'
}
})
const brandName = 'BrandName'
const logoSrc = '/images/brand/logo-full.svg'
const logoMonoSrc = '/images/brand/logo-full-mono.svg'
const iconSrc = '/images/brand/logo-icon.svg'
</script>
<style scoped>
.brand-logo {
display: inline-flex;
align-items: center;
text-decoration: none;
}
/* 尺寸变体 */
.size-sm .logo-image { height: 24px; }
.size-md .logo-image { height: 32px; }
.size-lg .logo-image { height: 48px; }
.size-sm .logo-icon { width: 24px; height: 24px; }
.size-md .logo-icon { width: 32px; height: 32px; }
.size-lg .logo-icon { width: 48px; height: 48px; }
/* Logo 保护空间 */
.logo-image {
display: block;
padding: calc(var(--logo-height) * 0.25); /* 25% 安全边距 */
}
</style>
Logo 使用规范
<template>
<div class="logo-showcase">
<!-- 标准使用 -->
<BrandLogo size="md" />
<!-- 最小尺寸限制 -->
<BrandLogo size="sm" /> <!-- 最小 24px -->
<!-- 单色版(深色背景) -->
<div class="dark-bg">
<BrandLogo mono />
</div>
<!-- 禁止用法提示 -->
<!-- ❌ 不要拉伸 -->
<!-- ❌ 不要旋转 -->
<!-- ❌ 不要添加阴影 -->
<!-- ❌ 不要使用非规定颜色 -->
</div>
</template>
组件库品牌定制
Tailwind CSS 品牌配置
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
brand: {
50: '#E6F0FF',
100: '#CCE0FF',
200: '#99C2FF',
300: '#66A3FF',
400: '#3385FF',
500: '#0066FF', // 主色
600: '#0052CC',
700: '#003D99',
800: '#002966',
900: '#001433',
},
// 语义色别名
primary: 'var(--brand-primary)',
secondary: 'var(--brand-secondary)',
},
fontFamily: {
display: ['Brand Display', 'sans-serif'],
body: ['Inter', 'sans-serif'],
},
borderRadius: {
brand: '8px', // 品牌圆角
},
boxShadow: {
brand: '0 4px 12px rgba(0, 102, 255, 0.15)',
}
}
}
}
Element Plus 主题定制
// styles/element-theme.scss
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
'base': #0066FF,
),
'success': (
'base': #22C55E,
),
'warning': (
'base': #F59E0B,
),
'danger': (
'base': #EF4444,
),
),
$font-family: (
'base': "'Inter', 'PingFang SC', sans-serif",
),
$border-radius: (
'base': 8px,
'small': 4px,
'round': 20px,
)
);
@use 'element-plus/theme-chalk/src/index.scss' as *;
品牌资源管理
资源目录结构
public/
└── brand/
├── logos/
│ ├── logo-full.svg
│ ├── logo-full-mono.svg
│ ├── logo-icon.svg
│ ├── logo-icon-mono.svg
│ └── logo-wordmark.svg
├── icons/
│ ├── icon-set.svg # 图标雪碧图
│ └── social/
│ ├── wechat.svg
│ └── weibo.svg
├── illustrations/
│ ├── hero-illustration.svg
│ └── empty-state.svg
└── brand-guidelines.pdf # 完整规范文档
资源引用辅助
// utils/brand-assets.ts
export const BRAND = {
logo: {
full: '/brand/logos/logo-full.svg',
fullMono: '/brand/logos/logo-full-mono.svg',
icon: '/brand/logos/logo-icon.svg',
wordmark: '/brand/logos/logo-wordmark.svg',
},
social: {
wechat: '/brand/icons/social/wechat.svg',
weibo: '/brand/icons/social/weibo.svg',
},
illustrations: {
hero: '/brand/illustrations/hero-illustration.svg',
emptyState: '/brand/illustrations/empty-state.svg',
}
}
// 使用
import { BRAND } from '@/utils/brand-assets'
<img :src="BRAND.logo.full" alt="Logo" />
品牌一致性检查
CSS 变量检查脚本
// scripts/check-brand-consistency.js
const fs = require('fs')
const path = require('path')
const css = require('css')
const BRAND_COLORS = ['#0066FF', '#FF6B35', '#00D4AA']
function checkCSSFiles(dir) {
const issues = []
function walkDir(currentPath) {
const files = fs.readdirSync(currentPath)
for (const file of files) {
const filePath = path.join(currentPath, file)
const stat = fs.statSync(filePath)
if (stat.isDirectory()) {
walkDir(filePath)
} else if (file.endsWith('.css') || file.endsWith('.scss')) {
const content = fs.readFileSync(filePath, 'utf8')
// 检查硬编码颜色
const hexColors = content.match(/#[0-9A-Fa-f]{6}/g) || []
for (const color of hexColors) {
if (!BRAND_COLORS.includes(color.toUpperCase())) {
issues.push({
file: filePath,
issue: `发现非品牌色: ${color}`,
suggestion: '请使用 CSS 变量 var(--brand-*)'
})
}
}
}
}
}
walkDir(dir)
return issues
}
const issues = checkCSSFiles('./src')
if (issues.length) {
console.log('品牌一致性检查发现问题:')
issues.forEach(i => console.log(` ${i.file}: ${i.issue}`))
process.exit(1)
}
Stylelint 规则
// .stylelintrc.js
module.exports = {
rules: {
// 禁止硬编码颜色
'color-no-hex': true,
// 必须使用 CSS 变量
'declaration-property-value-allowed-list': {
'color': ['/^var\\(--/', 'inherit', 'currentColor'],
'background-color': ['/^var\\(--/', 'transparent', 'inherit'],
},
// 禁止使用非品牌字体
'font-family-no-missing-generic-family-keyword': true,
}
}
总结
品牌指南技术实现的核心要点:
| 维度 | 实践 |
|---|---|
| 令牌化 | 将品牌规范拆解为设计令牌 |
| 分层 | 全局→语义→组件三层令牌 |
| 自动化 | Style Dictionary 多平台输出 |
| 组件化 | Logo、图标封装为组件 |
| 主题化 | 支持暗色模式和多主题 |
| 检查 | 自动化一致性校验 |
品牌不是静态的规范文档——它应该活在代码里,融入每个像素。当设计师说"主色",开发者应该条件反射地写出 var(--brand-primary)。这才是真正的品牌落地。


