mcpField
F
frontend-designer

frontend-designer

github.com/jamesrochabrun设计与 UI221337 次安装41 周前更新
代码仓库

关于 frontend-designer

frontend-designer 是面向前端设计师和开发者的界面开发技能,涵盖组件、设计系统、响应式布局与无障碍实践。还提供生成组件、初始化设计系统和执行无障碍审查的脚本。

主要功能

  1. 01构建可复用且易访问的组件
  2. 02建立基于设计令牌的系统
  3. 03应用移动优先响应式布局
  4. 04采用 React、Vue 和 Svelte 模式
  5. 05优化前端性能
  6. 06生成组件并审查无障碍性

适用场景

  1. 01创建具备键盘支持的界面组件
  2. 02为项目建立统一设计令牌
  3. 03让页面适配移动端与更大屏幕
  4. 04检查颜色对比度和语义 HTML

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jamesrochabrun/skills --skill frontend-designer

安装后在 Claude Code 中输入/frontend-designer确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
accessibility_checklist.md
component_library.md
design_tokens.md
responsive_patterns.md
scripts/
audit_accessibility.sh
generate_component.sh
setup_design_system.sh

详细介绍

前端设计师

一套全面的技能,帮助前端设计师和开发者运用现代最佳实践,构建美观、易访问且高性能的用户界面。

此技能的作用

帮助前端设计师/开发者完成以下工作:

  • 组件设计与开发 - 构建可复用、易访问的组件
  • 设计系统 - 实施设计令牌、模式和文档
  • 响应式设计 - 移动优先、流式布局
  • 无障碍访问(WCAG 2.1) - 包容性设计模式
  • 现代 CSS - Flexbox、Grid、自定义属性
  • 性能优化 - 快速、高效的前端
  • 框架模式 - React、Vue、Svelte 最佳实践
  • 设计转代码 - 从 Figma 到生产环境的工作流

此技能的重要性

缺乏系统化方法时:

  • 组件实现不一致
  • 存在无障碍访问问题
  • 响应式表现不佳
  • 代码和样式重复
  • 难以维护
  • 性能问题

使用此技能后:

  • 组件一致且可复用
  • 默认符合 WCAG AA 标准
  • 移动优先的响应式设计
  • 与设计系统保持一致
  • 代码库易于维护
  • 快速、高效地交付

核心原则

1. 无障碍优先
  • 至少符合 WCAG 2.1 AA 标准
  • 语义化 HTML
  • 键盘导航
  • 屏幕阅读器支持
  • 焦点管理
  • 色彩对比度
2. 移动优先的响应式设计
  • 从移动设备开始(320px)
  • 渐进增强
  • 流式排版
  • 灵活布局
  • 适合触控的目标区域
3. 默认注重性能
  • 尽量精简 CSS/JS
  • 延迟加载
  • 优化图像
  • 关键 CSS
  • 树摇优化
4. 组件驱动
  • 原子设计方法
  • 可复用组件
  • 基于 Props 的自定义
  • 组合优于继承
5. 与设计系统保持一致
  • 设计令牌
  • 间距一致
  • 排版比例
  • 调色板
  • 组件库

组件模式

按钮组件

易访问、灵活的按钮模式:

// React example
interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  loading?: boolean;
  children: React.ReactNode;
  onClick?: () => void;
}

export const Button: React.FC<ButtonProps> = ({
  variant = 'primary',
  size = 'md',
  disabled = false,
  loading = false,
  children,
  onClick,
  ...props
}) => {
  return (
    <button
      className={`btn btn--${variant} btn--${size}`}
      disabled={disabled || loading}
      onClick={onClick}
      aria-busy={loading}
      {...props}
    >
      {loading ? <Spinner /> : children}
    </button>
  );
};

CSS(使用设计令牌):

.btn {
  /* Base styles */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);

  font-family: var(--font-sans);
  font-weight: 600;
  text-decoration: none;

  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;

  transition: all 0.2s ease;

  /* Accessibility */
  min-height: 44px; /* WCAG touch target */

  &:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
  }

  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
}

/* Variants */
.btn--primary {
  background: var(--color-primary);
  color: var(--color-on-primary);

  &:hover:not(:disabled) {
    background: var(--color-primary-hover);
  }
}

.btn--secondary {
  background: var(--color-secondary);
  color: var(--color-on-secondary);
}

.btn--ghost {
  background: transparent;
  color: var(--color-primary);
  border: 1px solid currentColor;
}

/* Sizes */
.btn--sm {
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
}

.btn--md {
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-base);
}

.btn--lg {
  padding: var(--space-4) var(--space-6);
  font-size: var(--text-lg);
}
卡片组件

灵活、易访问的卡片:

interface CardProps {
  variant?: 'elevated' | 'outlined' | 'filled';
  padding?: 'none' | 'sm' | 'md' | 'lg';
  interactive?: boolean;
  children: React.ReactNode;
}

export const Card: React.FC<CardProps> = ({
  variant = 'elevated',
  padding = 'md',
  interactive = false,
  children,
}) => {
  const Component = interactive ? 'button' : 'div';

  return (
    <Component
      className={`
        card
        card--${variant}
        card--padding-${padding}
        ${interactive ? 'card--interactive' : ''}
      `}
      {...(interactive && { role: 'button', tabIndex: 0 })}
    >
      {children}
    </Component>
  );
};

CSS:

.card {
  border-radius: var(--radius-lg);
  background: var(--color-surface);
}

.card--elevated {
  box-shadow: var(--shadow-md);
}

.card--outlined {
  border: 1px solid var(--color-border);
}

.card--filled {
  background: var(--color-surface-variant);
}

.card--interactive {
  cursor: pointer;
  transition: transform 0.2s, box-shadow 0.2s;

  &:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-lg);
  }

  &:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
  }
}

.card--padding-sm { padding: var(--space-3); }
.card--padding-md { padding: var(--space-4); }
.card--padding-lg { padding: var(--space-6); }
表单输入组件

易访问的表单输入框:

interface InputProps {
  label: string;
  error?: string;
  hint?: string;
  required?: boolean;
  type?: 'text' | 'email' | 'password' | 'number';
}

export const Input: React.FC<InputProps> = ({
  label,
  error,
  hint,
  required = false,
  type = 'text',
  ...props
}) => {
  const id = useId();
  const hintId = `${id}-hint`;
  const errorId = `${id}-error`;

  return (
    <div className="input-wrapper">
      <label htmlFor={id} className="input-label">
        {label}
        {required && <span aria-label="required">*</span>}
      </label>

      {hint && (
        <p id={hintId} className="input-hint">
          {hint}
        </p>
      )}

      <input
        id={id}
        type={type}
        className={`input ${error ? 'input--error' : ''}`}
        aria-required={required}
        aria-invalid={!!error}
        aria-describedby={error ? errorId : hint ? hintId : undefined}
        {...props}
      />

      {error && (
        <p id={errorId} className="input-error" role="alert">
          {error}
        </p>
      )}
    </div>
  );
};

设计令牌

用于设计系统的 CSS 自定义属性:

:root {
  /* Colors - Primary */
  --color-primary: #0066FF;
  --color-primary-hover: #0052CC;
  --color-on-primary: #FFFFFF;

  /* Colors - Surface */
  --color-surface: #FFFFFF;
  --color-surface-variant: #F5F5F5;
  --color-on-surface: #1A1A1A;

  /* Colors - Borders */
  --color-border: #E0E0E0;
  --color-border-hover: #BDBDBD;

  /* Colors - Semantic */
  --color-error: #D32F2F;
  --color-success: #388E3C;
  --color-warning: #F57C00;
  --color-info: #1976D2;

  /* Spacing Scale (8px base) */
  --space-1: 0.25rem;  /* 4px */
  --space-2: 0.5rem;   /* 8px */
  --space-3: 0.75rem;  /* 12px */
  --space-4: 1rem;     /* 16px */
  --space-5: 1.5rem;   /* 24px */
  --space-6: 2rem;     /* 32px */
  --space-8: 3rem;     /* 48px */
  --space-10: 4rem;    /* 64px */

  /* Typography Scale */
  --text-xs: 0.75rem;    /* 12px */
  --text-sm: 0.875rem;   /* 14px */
  --text-base: 1rem;     /* 16px */
  --text-lg: 1.125rem;   /* 18px */
  --text-xl: 1.25rem;    /* 20px */
  --text-2xl: 1.5rem;    /* 24px */
  --text-3xl: 1.875rem;  /* 30px */
  --text-4xl: 2.25rem;   /* 36px */

  /* Font Families */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono: "SF Mono", Monaco, "Cascadia Code", monospace;

  /* Line Heights */
  --leading-tight: 1.25;
  --leading-normal: 1.5;
  --leading-relaxed: 1.75;

  /* Border Radius */
  --radius-sm: 0.25rem;  /* 4px */
  --radius-md: 0.5rem;   /* 8px */
  --radius-lg: 1rem;     /* 16px */
  --radius-full: 9999px;

  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
  --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);

  /* Focus Ring */
  --color-focus: #0066FF;

  /* Transitions */
  --transition-fast: 150ms ease;
  --transition-base: 200ms ease;
  --transition-slow: 300ms ease;
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --color-surface: #1A1A1A;
    --color-surface-variant: #2A2A2A;
    --color-on-surface: #FFFFFF;
    --color-border: #3A3A3A;
  }
}

响应式设计模式

移动优先的断点
/* Mobile-first approach */
.container {
  padding: var(--space-4);

  /* Tablet: 768px and up */
  @media (min-width: 48rem) {
    padding: var(--space-6);
  }

  /* Desktop: 1024px and up */
  @media (min-width: 64rem) {
    padding: var(--space-8);
    max-width: 1200px;
    margin: 0 auto;
  }
}
流式排版
/* Responsive typography */
h1 {
  font-size: clamp(2rem, 5vw, 3.5rem);
  line-height: var(--leading-tight);
}

h2 {
  font-size: clamp(1.5rem, 4vw, 2.5rem);
}

p {
  font-size: clamp(1rem, 2vw, 1.125rem);
  line-height: var(--leading-normal);
}
网格布局
/* Responsive grid */
.grid {
  display: grid;
  gap: var(--space-4);

  /* Auto-fit columns (min 280px) */
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

/* 12-column grid system */
.grid-12 {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--space-4);
}

.col-span-4 {
  grid-column: span 4;
}

/* Stack on mobile */
@media (max-width: 48rem) {
  .col-span-4 {
    grid-column: span 12;
  }
}

无障碍模式

跳转链接
export const SkipLink = () => (
  <a href="#main-content" className="skip-link">
    Skip to main content
  </a>
);
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: var(--color-primary);
  color: var(--color-on-primary);
  padding: var(--space-2) var(--space-4);
  text-decoration: none;
  z-index: 100;

  &:focus {
    top: 0;
  }
}
焦点管理
// Modal with focus trap
export const Modal = ({ isOpen, onClose, children }) => {
  const modalRef = useRef(null);

  useEffect(() => {
    if (isOpen) {
      // Save currently focused element
      const previouslyFocused = document.activeElement;

      // Focus first focusable element in modal
      const firstFocusable = modalRef.current?.querySelector(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
      );
      firstFocusable?.focus();

      // Restore focus on close
      return () => previouslyFocused?.focus();
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div
      ref={modalRef}
      role="dialog"
      aria-modal="true"
      className="modal-overlay"
    >
      <div className="modal">
        {children}
      </div>
    </div>
  );
};
ARIA 标签
// Icon button with accessible label
export const IconButton = ({ icon, label, ...props }) => (
  <button
    aria-label={label}
    className="icon-button"
    {...props}
  >
    <span aria-hidden="true">{icon}</span>
  </button>
);

// Loading state
export const LoadingButton = ({ loading, children, ...props }) => (
  <button
    aria-busy={loading}
    aria-live="polite"
    disabled={loading}
    {...props}
  >
    {loading ? 'Loading...' : children}
  </button>
);

性能优化

关键 CSS

<style>
  /* Above-the-fold styles */
  body { margin: 0; font-family: var(--font-sans); }
  .header { /* critical header styles */ }
  .hero { /* critical hero styles */ }
</style>


<link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">
图像延迟加载
export const LazyImage = ({ src, alt, ...props }) => (
  <img
    src={src}
    alt={alt}
    loading="lazy"
    decoding="async"
    {...props}
  />
);
代码拆分
// React lazy loading
const Dashboard = lazy(() => import('./Dashboard'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Dashboard />
    </Suspense>
  );
}

现代 CSS 技术

容器查询
.card {
  container-type: inline-size;
}

.card-content {
  display: flex;
  flex-direction: column;

  /* Switch to row layout when container > 400px */
  @container (min-width: 400px) {
    flex-direction: row;
  }
}
CSS Grid 自动适配
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: var(--space-4);
}
使用自定义属性实现主题
/* Light theme (default) */
:root {
  --bg: #ffffff;
  --text: #000000;
}

/* Dark theme */
[data-theme="dark"] {
  --bg: #000000;
  --text: #ffffff;
}

body {
  background: var(--bg);
  color: var(--text);
}

组件库结构

src/
├── components/
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.css
│   │   ├── Button.test.tsx
│   │   └── Button.stories.tsx
│   ├── Card/
│   ├── Input/
│   └── index.ts
├── tokens/
│   ├── colors.css
│   ├── spacing.css
│   ├── typography.css
│   └── index.css
├── utils/
│   ├── a11y.ts
│   └── responsive.ts
└── index.ts

使用此技能

生成组件
./scripts/generate_component.sh Button

创建以下内容:

  • TypeScript/JSX 文件
  • CSS 模块
  • 测试文件
  • Storybook 故事
  • 无障碍检查
设置设计系统
./scripts/setup_design_system.sh

创建以下内容:

  • 设计令牌(CSS 自定义属性)
  • 基础样式
  • 组件模板
  • 文档结构
无障碍审查
./scripts/audit_accessibility.sh

检查以下内容:

  • 色彩对比度
  • 键盘导航
  • ARIA 属性
  • 语义化 HTML
  • 焦点管理

最佳实践

组件设计

✅ 应该:

  • 使用语义化 HTML
  • 确保组件支持键盘操作
  • 提供 ARIA 标签
  • 支持浅色和深色模式
  • 将触控目标区域设为至少 44x44px
  • 使用正确的标题层级
  • 处理加载和错误状态

❌ 不应该:

  • 用 div 充当按钮
  • 忘记焦点样式
  • 将颜色硬编码
  • 忽略移动设备视口
  • 忽略图像的替代文本
  • 创建无法访问的模态框
CSS 最佳实践

✅ 应该:

  • 使用设计令牌
  • 采用移动优先的响应式设计
  • 使用 BEM 或 CSS 模块命名
  • 使用逻辑属性(inline-start 而非 left)
  • 使用现代布局(flexbox、grid)

❌ 不应该:

  • 使用 !important
  • 过度嵌套(超过 3 层)
  • 到处使用固定像素值
  • 使用特定于浏览器的 hack
  • 使用内联样式
性能

✅ 应该:

  • 延迟加载图片
  • 对路由进行代码分割
  • 精简 CSS
  • 使用系统字体
  • 优化图片(WebP)
  • 内联关键 CSS

❌ 不应该:

  • 加载未使用的 CSS
  • 使用大型 JavaScript 包
  • 使用未经优化的图片
  • 使用阻塞资源
  • 使用过多网页字体

特定框架的模式

React
// Composition pattern
export const Card = ({ children }) => (
  <div className="card">{children}</div>
);

export const CardHeader = ({ children }) => (
  <div className="card-header">{children}</div>
);

// Usage
<Card>
  <CardHeader>Title</CardHeader>
  <CardBody>Content</CardBody>
</Card>
Vue

<template>
  <div :class="cardClasses">
    <slot />
  </div>
</template>

<script setup>
const props = defineProps({
  variant: String,
  padding: String
});

const cardClasses = computed(() => [
  'card',
  `card--${props.variant}`,
  `card--padding-${props.padding}`
]);
</script>

资源

包含所有参考资料:

  • 设计令牌系统
  • 无障碍检查清单(WCAG 2.1)
  • 响应式设计模式
  • 组件库模板
  • 性能优化指南

总结

此技能提供:

  • 无障碍组件 - 默认符合 WCAG AA
  • 响应式设计 - 移动优先的方法
  • 设计系统 - 基于令牌的一致性
  • 现代 CSS - Flexbox、Grid、自定义属性
  • 性能 - 优化交付
  • 最佳实践 - 可用于生产环境的模式

使用此技能构建美观、无障碍、高性能的前端。


“优秀的设计就是无障碍设计。”

评论

支持 Markdown