Skip to content
Back to skills

Web Component Design

ASecurity

当构建 React/Vue/Svelte 组件库、设计组件 API 或落地前端设计系统时使用;做组合模式选型、CSS-in-JS 方案选择与可复用组件 API 的设计落地;不适用于纯样式微调、单页面一次性 UI 或后端逻辑;触发词:组件库、设计系统、复合组件、组件 API、CSS-in-JS

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 19, 2026
ai-agentsreactvuenodeapifrontend

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Scanned September 19, 2026

npx -y skills add findscripter/everything-skills --skill web-component-design --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Web Component Design?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Web Component Design
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/findscripter-web-component-design/badge)](https://www.skillsdirectory.com/skills/findscripter-web-component-design)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: web-component-design
title: Web 组件设计模式
description: 当构建 React/Vue/Svelte 组件库、设计组件 API 或落地前端设计系统时使用;做组合模式选型、CSS-in-JS 方案选择与可复用组件 API 的设计落地;不适用于纯样式微调、单页面一次性 UI 或后端逻辑;触发词:组件库、设计系统、复合组件、组件 API、CSS-in-JS
domain: 研发/frontend
triggers: [组件库, 设计系统, 复合组件, render props, 组件 API 设计, CSS-in-JS, Tailwind, styled-components, 可复用组件, Vue 插槽, Svelte runes, forwardRef]
tags: [前端, 组件设计, 设计系统, react, vue, svelte, css-in-js, ui]
level: 进阶
status: stable
agents: [claude-code, codex, cursor, gemini-cli]
tools: [React, Vue 3, Svelte 5, Tailwind CSS, class-variance-authority, styled-components, Emotion, CSS Modules, Vanilla Extract]
requires: []
related: []
combines_with: []
license: MIT
source: wshobson/agents
source_license: MIT
---
采用现代框架,用清晰的组合模式与样式方案,构建可复用、易维护的 UI 组件。

## 何时使用

适用:
- 设计可复用组件库或设计系统
- 实现复杂的组件组合模式(复合组件 / render props / 插槽)
- 选型并落地 CSS-in-JS 方案
- 构建无障碍、响应式的 UI 组件
- 在整个代码库中统一组件 API 约定
- 将遗留组件重构为现代模式

不该用(负边界):
- 仅做单个页面的一次性 UI 或局部样式微调,不涉及复用
- 纯视觉/CSS 调整且无组件抽象需求
- 后端业务逻辑或数据层问题

## 步骤

1. 明确复用边界:判断是「一次性 UI」还是「需沉淀到组件库」,只有后者才走本流程。
2. 选组合模式:
   - 复合组件(Compound Components):一组协同工作的关联组件,通过 Context 共享状态。
   - Render Props:把渲染权委托给父级。
   - 插槽 Slots(Vue/Svelte):具名内容注入点。
3. 选样式方案(见下表),与团队既有技术栈对齐。
4. 设计组件 API:语义化 prop 名、合理默认值、用 `children` 支持组合、用 `className`/`style` 允许样式覆盖。
5. 补齐无障碍与质量:ARIA 属性、键盘支持、forwardRef、必要的 memo。
6. 重构既有组件时,先识别 prop 爆炸/重渲染级联等坏味道再下手。

### CSS-in-JS 方案选型

| 方案 | 思路 | 最适合 |
| --- | --- | --- |
| Tailwind CSS | 原子类 | 快速原型、设计系统 |
| CSS Modules | 局部作用域 CSS 文件 | 既有 CSS、渐进采用 |
| styled-components | 模板字符串 | React、动态样式 |
| Emotion | 对象/模板样式 | 灵活、SSR 友好 |
| Vanilla Extract | 零运行时 | 性能敏感型应用 |

## 指令

组件 API 设计原则:
- 用语义化 prop 名(`isLoading` 优于 `loading`)。
- 提供合理默认值(defaultVariants)。
- 通过 `children` 支持组合,避免大量配置型 prop。
- 通过 `className` / `style` 允许样式覆盖。

最佳实践:
1. 单一职责:每个组件把一件事做好。
2. 防止 prop 钻取:深层嵌套数据用 Context 传递。
3. 默认无障碍:内置 ARIA 属性与键盘支持。
4. 受控 vs 非受控:在合适场景同时支持两种模式。
5. 转发 ref:用 `forwardRef` 让父级访问 DOM 节点。
6. 记忆化:对昂贵渲染用 `React.memo` / `useMemo`。
7. 错误边界:包裹可能失败的组件。

## 示例

React + Tailwind 按钮(用 cva 管理变体 + forwardRef):

```tsx
import { forwardRef, type ComponentPropsWithoutRef } from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        primary: "bg-blue-600 text-white hover:bg-blue-700",
        secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200",
        ghost: "hover:bg-gray-100 hover:text-gray-900",
      },
      size: {
        sm: "h-8 px-3 text-sm",
        md: "h-10 px-4 text-sm",
        lg: "h-12 px-6 text-base",
      },
    },
    defaultVariants: { variant: "primary", size: "md" },
  },
);

interface ButtonProps
  extends ComponentPropsWithoutRef<"button">,
    VariantProps<typeof buttonVariants> {
  isLoading?: boolean;
}

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, isLoading, children, ...props }, ref) => (
    <button
      ref={ref}
      className={cn(buttonVariants({ variant, size }), className)}
      disabled={isLoading || props.disabled}
      {...props}
    >
      {isLoading && <Spinner className="mr-2 h-4 w-4" />}
      {children}
    </button>
  ),
);
Button.displayName = "Button";
```

React 复合组件(Context 驱动的 Accordion):

```tsx
import { createContext, useContext, useState, type ReactNode } from "react";

interface AccordionContextValue {
  openItems: Set<string>;
  toggle: (id: string) => void;
}

const AccordionContext = createContext<AccordionContextValue | null>(null);

function useAccordion() {
  const context = useContext(AccordionContext);
  if (!context) throw new Error("Must be used within Accordion");
  return context;
}

export function Accordion({ children }: { children: ReactNode }) {
  const [openItems, setOpenItems] = useState<Set<string>>(new Set());
  const toggle = (id: string) => {
    setOpenItems((prev) => {
      const next = new Set(prev);
      next.has(id) ? next.delete(id) : next.add(id);
      return next;
    });
  };
  return (
    <AccordionContext.Provider value={{ openItems, toggle }}>
      <div className="divide-y">{children}</div>
    </AccordionContext.Provider>
  );
}

Accordion.Item = function AccordionItem({
  id, title, children,
}: { id: string; title: string; children: ReactNode }) {
  const { openItems, toggle } = useAccordion();
  const isOpen = openItems.has(id);
  return (
    <div>
      <button onClick={() => toggle(id)} className="w-full text-left py-3">
        {title}
      </button>
      {isOpen && <div className="pb-3">{children}</div>}
    </div>
  );
};
```

复合组件的使用形态:

```tsx
<Select value={value} onChange={setValue}>
  <Select.Trigger>Choose option</Select.Trigger>
  <Select.Options>
    <Select.Option value="a">Option A</Select.Option>
    <Select.Option value="b">Option B</Select.Option>
  </Select.Options>
</Select>
```

Vue 3 用 provide/inject 实现 Tabs 上下文:

```vue
<script setup lang="ts">
import { ref, computed, provide, inject, type InjectionKey, type Ref } from "vue";

interface TabsContext {
  activeTab: Ref<string>;
  setActive: (id: string) => void;
}
const TabsKey: InjectionKey<TabsContext> = Symbol("tabs");

// 父组件
const activeTab = ref("tab-1");
provide(TabsKey, {
  activeTab,
  setActive: (id: string) => { activeTab.value = id; },
});

// 子组件使用
const tabs = inject(TabsKey);
const isActive = computed(() => tabs?.activeTab.value === props.id);
</script>
```

Svelte 5 用 runes(`$props` / `$derived`)写按钮:

```svelte
<script lang="ts">
  interface Props {
    variant?: 'primary' | 'secondary';
    size?: 'sm' | 'md' | 'lg';
    onclick?: () => void;
    children: import('svelte').Snippet;
  }
  let { variant = 'primary', size = 'md', onclick, children }: Props = $props();
  const classes = $derived(`btn btn-${variant} btn-${size}`);
</script>

<button class={classes} {onclick}>
  {@render children()}
</button>
```

## 注意事项

常见坑:
- Prop 爆炸:prop 太多时改用组合而非配置。
- 样式冲突:用局部作用域样式或 CSS Modules 隔离。
- 重渲染级联:用 React DevTools 定位,恰当地 memo。
- 无障碍缺口:用屏幕阅读器与键盘导航实测。
- 包体积:对未使用的组件变体做 tree-shaking。

## 互见

- 设计系统 token 与主题方案
- 前端无障碍(a11y)与 ARIA 实践
- 前端性能优化与渲染分析

---

采编自 wshobson/agents(MIT 许可)。

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…