agentsclimarketplace

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

Install
npx -y skills add findscripter/everything-skills --skill web-component-design

Assembled 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 调整且无组件抽象需求
  • 后端业务逻辑或数据层问题

步骤

  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):

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.

Keep looking

Skills are one crate of 326,614. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.