Web component design
Skill findscripter/everything-skills/02-engineering/web-component-design
当构建 React/Vue/Svelte 组件库、设计组件 API 或落地前端设计系统时使用;做组合模式选型、CSS-in-JS 方案选择与可复用组件 API 的设计落地;不适用于纯样式微调、单页面一次性 UI 或后端逻辑;触发词:组件库、设计系统、复合组件、组件 API、CSS-in-JSFrom its SKILL.md
npx -y skills add findscripter/everything-skills --skill web-component-designAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
8.0 KB, ~2.2k tokens by cl100k_base, as published. Nobody here has run it
采用现代框架,用清晰的组合模式与样式方案,构建可复用、易维护的 UI 组件。
何时使用
适用:
- 设计可复用组件库或设计系统
- 实现复杂的组件组合模式(复合组件 / render props / 插槽)
- 选型并落地 CSS-in-JS 方案
- 构建无障碍、响应式的 UI 组件
- 在整个代码库中统一组件 API 约定
- 将遗留组件重构为现代模式
不该用(负边界):
- 仅做单个页面的一次性 UI 或局部样式微调,不涉及复用
- 纯视觉/CSS 调整且无组件抽象需求
- 后端业务逻辑或数据层问题
步骤
- 明确复用边界:判断是「一次性 UI」还是「需沉淀到组件库」,只有后者才走本流程。
- 选组合模式:
- 复合组件(Compound Components):一组协同工作的关联组件,通过 Context 共享状态。
- Render Props:把渲染权委托给父级。
- 插槽 Slots(Vue/Svelte):具名内容注入点。
- 选样式方案(见下表),与团队既有技术栈对齐。
- 设计组件 API:语义化 prop 名、合理默认值、用
children支持组合、用className/style允许样式覆盖。 - 补齐无障碍与质量:ARIA 属性、键盘支持、forwardRef、必要的 memo。
- 重构既有组件时,先识别 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允许样式覆盖。
最佳实践:
- 单一职责:每个组件把一件事做好。
- 防止 prop 钻取:深层嵌套数据用 Context 传递。
- 默认无障碍:内置 ARIA 属性与键盘支持。
- 受控 vs 非受控:在合适场景同时支持两种模式。
- 转发 ref:用
forwardRef让父级访问 DOM 节点。 - 记忆化:对昂贵渲染用
React.memo/useMemo。 - 错误边界:包裹可能失败的组件。
示例
React + Tailwind 按钮(用 cva 管理变体 + forwardRef):
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):
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>
);
};
复合组件的使用形态:
<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 上下文:
<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)写按钮:
<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 许可)。
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.