UI设计技能(Web) ui-web

这个技能涵盖了Web UI设计的现代趋势和技术,包括玻璃形态效果、Tailwind CSS框架的应用、暗色模式的实现,以及确保可访问性的核心原则。关键词包括:玻璃形态、Tailwind、暗色模式、可访问性。

前端开发 0 次安装 0 次浏览 更新于 3/5/2026

UI设计技能(Web)

加载方式:base.md + react-web.md


强制性:WCAG 2.1 AA合规性

这些规则是不可协商的。每个UI元素都必须通过这些检查。

1. 颜色对比度(关键)

文本对比度要求:
├── 普通文本(<18px):最低4.5:1
├── 大号文本(≥18px粗体或≥24px):最低3:1
├── UI组件(按钮,输入框):最低3:1
└── 焦点指示器:最低3:1

禁止的颜色组合:
✗ gray-400在白色上(#9CA3AF在#FFFFFF上=2.6:1)-失败
✗ gray-500在白色上(#6B7280在#FFFFFF上=4.6:1)-勉强通过
✗ 白色在黄色上-失败
✗ 浅蓝色在白色上-通常失败

安全的颜色组合:
✓ gray-700在白色上(#374151在#FFFFFF上=9.2:1)
✓ gray-600在白色上(#4B5563在#FFFFFF上=6.4:1)
✓ gray-900在白色上(#111827在#FFFFFF上=16:1)
✓ 白色在gray-900,blue-600,green-700上

2. 可见性规则(关键)

所有按钮必须有:
✓ 可见的背景颜色或可见的边框(最小1px)
✓ 与背景形成对比的文本颜色
✓ 最小高度:44px(触摸目标)
✓ 内边距:至少px-4 py-2

永远不要创建:
✗ 透明背景且没有边框的按钮
✗ 文本与背景同色
✗ 没有可见边框的幽灵按钮
✗ 白色文本在浅色背景上
✗ 深色文本在深色背景上

3. 必需的元素样式

// 每个按钮都需要可见的边界
// 主要:实心背景
<button className="bg-gray-900 text-white px-4 py-3 rounded-lg">
  主要
</button>

// 次要:可见背景
<button className="bg-gray-100 text-gray-900 px-4 py-3 rounded-lg">
  次要
</button>

// 幽灵:必须有可见的边框
<button className="border border-gray-300 text-gray-700 px-4 py-3 rounded-lg">
  幽灵
</button>

// 永远不要这样做:
<button className="text-gray-500">隐形按钮</button> // ✗没有边界
<button className="bg-white text-white">隐藏</button>     // ✗没有对比度

4. 焦点状态(必需)

// 每个交互元素都需要明显的焦点
className="focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"

// 永远不要移除焦点而不替换
className="outline-none" // ✗没有环替换是禁止的

5. 暗色模式对比度

在实现暗色模式时:
├── 文本必须是浅色(gray-100到白色)在深色背景上
├── 边框必须是可见的(gray-700或更亮)
├── 永远不要使用gray-400文本在gray-900背景上(对比度失败)
└── 在发货前测试两种模式

暗色模式安全文本:
✓ text-white在bg-gray-900上
✓ text-gray-100在bg-gray-800上
✓ text-gray-200在bg-gray-900上

不安全(对比度失败):
✗ text-gray-500在bg-gray-900上(2.4:1)
✗ text-gray-400在bg-gray-800上(3.1:1)

核心理念

美丽的UI不是装饰 - 它是沟通。 每个视觉选择都应该服务于清晰度、层次结构和用户信心。默认选择优雅和克制。

设计原则

1. 视觉层次

主要动作    → 粗体,高对比度,突出
次要动作  → 微妙,低对比度
三级/链接    → 最小化,文本样式

2. 间距系统(8px网格)

// Tailwind间距比例 - 一致使用
const spacing = {
  xs: 'p-1',      // 4px  - 紧凑内部
  sm: 'p-2',      // 8px  - 紧凑
  md: 'p-4',      // 16px - 默认
  lg: 'p-6',      // 24px - 舒适
  xl: 'p-8',      // 32px - 宽敞
  '2xl': 'p-12',  // 48px - 部分间隙
};

// 规则:更多的空白=更高级的感觉
// 规则:一致的间距>完美的间距

3. 排版规模

// 每页限制3-4种字体大小
const typography = {
  hero: 'text-4xl md:text-5xl font-bold tracking-tight',
  heading: 'text-2xl md:text-3xl font-semibold',
  subheading: 'text-lg md:text-xl font-medium',
  body: 'text-base leading-relaxed',
  caption: 'text-sm text-gray-500',
};

// 规则:永远不要使用超过2种字体
// 规则:正文行高1.5-1.7

玻璃形态(Web)

基础玻璃卡片

// 现代玻璃效果 - 慎用以强调
const GlassCard = ({ children, className = '' }) => (
  <div className={`
    backdrop-blur-xl
    bg-white/10
    border border-white/20
    rounded-2xl
    shadow-xl
    shadow-black/5
    ${className}
  `}>
    {children}
  </div>
);

玻璃变体

// 浅色模式玻璃
const lightGlass = `
  backdrop-blur-xl
  bg-white/70
  border border-white/50
  shadow-lg shadow-gray-200/50
`;

// 深色模式玻璃
const darkGlass = `
  backdrop-blur-xl
  bg-gray-900/70
  border border-white/10
  shadow-xl shadow-black/20
`;

// 磨砂侧边栏
const frostedSidebar = `
  backdrop-blur-2xl
  bg-gradient-to-b from-white/80 to-white/60
  border-r border-white/30
`;

// 浮动动作玻璃
const floatingGlass = `
  backdrop-blur-md
  bg-white/90
  rounded-full
  shadow-lg shadow-black/10
  border border-white/50
`;

使用玻璃形态时

✓ 带有图像背景的英雄部分
✓ 覆盖渐变的浮动卡片
✓ 模态覆盖
✓ 导航栏(微妙)
✓ 功能亮点

✗ 每个卡片(过度使用会削弱效果)
✗ 文本密集的内容区域
✗ 表单(降低对比度)
✗ 数据表格

颜色系统

语义颜色

const colors = {
  // 动作
  primary: 'bg-blue-600 hover:bg-blue-700',
  secondary: 'bg-gray-100 hover:bg-gray-200 text-gray-900',
  danger: 'bg-red-600 hover:bg-red-700',
  success: 'bg-green-600 hover:bg-green-700',

  // 表面
  background: 'bg-gray-50 dark:bg-gray-950',
  surface: 'bg-white dark:bg-gray-900',
  elevated: 'bg-white dark:bg-gray-800 shadow-lg',

  // 文本
  textPrimary: 'text-gray-900 dark:text-white',
  textSecondary: 'text-gray-600 dark:text-gray-400',
  textMuted: 'text-gray-400 dark:text-gray-500',
};

渐变背景

// 微妙的网格渐变(现代,高级)
const meshGradient = `
  bg-gradient-to-br
  from-blue-50 via-white to-purple-50
  dark:from-gray-950 dark:via-gray-900 dark:to-gray-950
`;

// 鲜艳的英雄渐变
const heroGradient = `
  bg-gradient-to-r
  from-blue-600 via-purple-600 to-pink-600
`;

// 微妙的径向光晕
const radialGlow = `
  bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))]
  from-blue-200/40 via-transparent to-transparent
`;

组件模式

按钮

// 主要按钮 - 粗体,自信
const PrimaryButton = ({ children, ...props }) => (
  <button
    className="
      px-6 py-3
      bg-gray-900 dark:bg-white
      text-white dark:text-gray-900
      font-medium
      rounded-xl
      transition-all duration-200
      hover:bg-gray-800 dark:hover:bg-gray-100
      hover:shadow-lg hover:shadow-gray-900/20
      active:scale-[0.98]
      disabled:opacity-50 disabled:cursor-not-allowed
    "
    {...props}
  >
    {children}
  </button>
);

// 次要按钮 - 微妙
const SecondaryButton = ({ children, ...props }) => (
  <button
    className="
      px-6 py-3
      bg-gray-100 dark:bg-gray-800
      text-gray-900 dark:text-white
      font-medium
      rounded-xl
      transition-all duration-200
      hover:bg-gray-200 dark:hover:bg-gray-700
      active:scale-[0.98]
    "
    {...props}
  >
    {children}
  </button>
);

// 幽灵按钮 - 最小化
const GhostButton = ({ children, ...props }) => (
  <button
    className="
      px-4 py-2
      text-gray-600 dark:text-gray-400
      font-medium
      rounded-lg
      transition-colors duration-200
      hover:text-gray-900 dark:hover:text-white
      hover:bg-gray-100 dark:hover:bg-gray-800
    "
    {...props}
  >
    {children}
  </button>
);

卡片

// 干净的卡片,微妙的抬高
const Card = ({ children, className = '' }) => (
  <div className={`
    bg-white dark:bg-gray-900
    rounded-2xl
    border border-gray-200 dark:border-gray-800
    shadow-sm
    hover:shadow-md
    transition-shadow duration-300
    ${className}
  `}>
    {children}
  </div>
);

// 交互式卡片
const InteractiveCard = ({ children, onClick }) => (
  <button
    onClick={onClick}
    className="
      w-full text-left
      bg-white dark:bg-gray-900
      rounded-2xl
      border border-gray-200 dark:border-gray-800
      p-6
      transition-all duration-300
      hover:border-gray-300 dark:hover:border-gray-700
      hover:shadow-lg
      hover:-translate-y-1
      active:scale-[0.99]
    "
  >
    {children}
  </button>
);

输入字段

const Input = ({ label, error, ...props }) => (
  <div className="space-y-2">
    {label && (
      <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
        {label}
      </label>
    )}
    <input
      className={`
        w-full px-4 py-3
        bg-gray-50 dark:bg-gray-800
        border-2 rounded-xl
        text-gray-900 dark:text-white
        placeholder-gray-400 dark:placeholder-gray-500
        transition-all duration-200
        focus:outline-none focus:ring-0
        ${error
          ? 'border-red-500 focus:border-red-500'
          : 'border-transparent focus:border-blue-500 focus:bg-white dark:focus:bg-gray-900'
        }
      `}
      {...props}
    />
    {error && (
      <p className="text-sm text-red-500">{error}</p>
    )}
  </div>
);

微交互

过渡

// 标准过渡 - 总是使用
const transitions = {
  fast: 'transition-all duration-150',      // 悬停状态
  normal: 'transition-all duration-200',    // 大多数交互
  slow: 'transition-all duration-300',      // 卡片悬停,模态
  spring: 'transition-all duration-500 ease-out', // 页面过渡
};

// 规则:所有交互的都应该过渡
// 规则:150-300ms感觉响应迅速,>500ms感觉慢

悬停效果

// 悬停时缩放(按钮,卡片)
className="hover:scale-105 active:scale-95 transition-transform"

// 悬停时抬起(卡片)
className="hover:-translate-y-1 hover:shadow-xl transition-all"

// 悬停时发光(CTAs)
className="hover:shadow-lg hover:shadow-blue-500/25 transition-shadow"

// 悬停时边框高亮(输入框,卡片)
className="hover:border-gray-300 transition-colors"

加载状态

// 骨架加载器
const Skeleton = ({ className = '' }) => (
  <div className={`
    animate-pulse
    bg-gray-200 dark:bg-gray-800
    rounded-lg
    ${className}
  `} />
);

// 旋转器
const Spinner = ({ size = 'md' }) => (
  <div className={`
    animate-spin rounded-full
    border-2 border-gray-200 dark:border-gray-700
    border-t-blue-600
    ${size === 'sm' ? 'w-4 h-4' : size === 'lg' ? 'w-8 h-8' : 'w-6 h-6'}
  `} />
);

// 按钮加载状态
<button disabled className="relative">
  <span className="opacity-0">提交</span>
  <Spinner className="absolute inset-0 m-auto" />
</button>

布局模式

容器

// 一致的最大宽度和填充
const Container = ({ children, className = '' }) => (
  <div className={`
    max-w-7xl mx-auto
    px-4 sm:px-6 lg:px-8
    ${className}
  `}>
    {children}
  </div>
);

部分间距

// 一致的垂直节奏
const Section = ({ children }) => (
  <section className="py-16 md:py-24">
    <Container>{children}</Container>
  </section>
);

网格系统

// 功能网格
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  {features.map(f => <FeatureCard key={f.id} {...f} />)}
</div>

// Bento网格(现代不对称)
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
  <div className="col-span-2 row-span-2">大号</div>
  <div className="col-span-1">小号</div>
  <div className="col-span-1">小号</div>
  <div className="col-span-2">中号</div>
</div>

暗色模式

实施

// 总是为两种模式设计
// 使用CSS变量或Tailwind dark:前缀

// 主题切换
const ThemeToggle = () => {
  const [dark, setDark] = useState(false);

  useEffect(() => {
    document.documentElement.classList.toggle('dark', dark);
  }, [dark]);

  return (
    <button onClick={() => setDark(!dark)}>
      {dark ? <SunIcon /> : <MoonIcon />}
    </button>
  );
};

颜色配对

浅色模式         深色模式
─────────────────────────────────
白色               gray-950
gray-50             gray-900
gray-100            gray-800
gray-200            gray-700
gray-900(文本)    白色(文本)
gray-600(次要)    gray-400
blue-600            blue-500

可访问性

对比度要求

WCAG AA: 普通文本4.5:1,大号文本3:1
WCAG AAA: 普通文本7:1,大号文本4.5:1

// 测试:使用浏览器开发者工具或对比度检查器
// 规则:永远不要使用gray-400在白色上作为正文文本

焦点状态

// 总是可见的焦点环
className="
  focus:outline-none
  focus-visible:ring-2
  focus-visible:ring-blue-500
  focus-visible:ring-offset-2
"

// 永远不要移除焦点样式而不替换
// ✗ outline-none(单独)
// ✓ outline-none + focus-visible:ring

屏幕阅读器

// 视觉上隐藏但可访问
const srOnly = "absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0";

// 图标按钮需要标签
<button aria-label="关闭菜单">
  <XIcon className="w-6 h-6" />
</button>

// 宣布动态内容
<div role="status" aria-live="polite">
  {message}
</div>

反模式

永远不要做

✗ 一页上超过3种字体大小
✗ 随机间距值(使用8px网格)
✗ 纯黑色(#000)在纯白色(#fff)上
✗ 彩色文本在彩色背景上而不检查对比度
✗ 动画超过500ms的UI元素
✗ 到处都是玻璃形态
✗ 每个地方都有下拉阴影
✗ 渐变在文本上(难以阅读)
✗ 不能停止的自动播放动画
✗ 移除焦点指示器
✗ 灰色文本低于4.5:1对比度
✗ 小点击目标(< 44px)

常见错误

// ✗ 太多阴影
className="shadow-sm shadow-md shadow-lg" // 选择一个

// ✗ 不一致的圆角
className="rounded-sm rounded-lg rounded-2xl" // 系统:sm,lg,xl,2xl

// ✗ 竞争焦点
// 每个视图中一个主要CTA

// ✗ 过度装饰
// 如果它不服务于功能,就移除它

快速参考

现代默认值

// 边框半径:12-16px(rounded-xl到rounded-2xl)
// 阴影:微妙(shadow-sm到shadow-md)
// 字体:Inter,SF Pro,system-ui
// 主要:近黑色或品牌颜色
// 过渡:200ms缓出
// 间距:8px网格(Tailwind默认)

高级感觉清单

□ 慷慨的空白
□ 微妙的阴影(不刺眼)
□ 所有交互的平滑过渡
□ 一致的边框半径
□ 有限的调色板(最多3种颜色)
□ 排版层次(最多3种大小)
□ 高质量的图像
□ 悬停/焦点的微交互
□ 暗色模式支持