重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
component-architecture by mgd34msu/goodvibes-plugin
npx skills add https://github.com/mgd34msu/goodvibes-plugin --skill component-architecturescripts/
validate-components.sh
references/
component-patterns.md
此技能将引导您使用 GoodVibes 的精准分析工具来设计和实现 UI 组件。当您需要构建具有适当组合、状态管理和性能优化的 React、Vue 或 Svelte 组件时,请使用此工作流。
在以下情况下加载此技能:
触发短语:"构建组件"、"创建 UI"、"组件结构"、"渲染优化"、"状态提升"、"组件组合"。
在构建组件之前,先了解代码库中现有的模式。
使用 discover 查找所有组件文件并了解组织模式。
discover:
queries:
- id: react_components
type: glob
patterns: ["**/*.tsx", "**/*.jsx"]
- id: vue_components
type: glob
patterns: ["**/*.vue"]
- id: svelte_components
type: glob
patterns: ["**/*.svelte"]
verbosity: files_only
这将揭示:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
使用 discover 查找组合模式、状态管理和样式方法。
discover:
queries:
- id: composition_patterns
type: grep
pattern: "(children|render|slot|as\\s*=)"
glob: "**/*.{tsx,jsx,vue,svelte}"
- id: state_management
type: grep
pattern: "(useState|useReducer|reactive|writable|createSignal)"
glob: "**/*.{ts,tsx,js,jsx,vue,svelte}"
- id: styling_approach
type: grep
pattern: "(className|styled|css|tw`|@apply)"
glob: "**/*.{tsx,jsx,vue,svelte}"
- id: performance_hooks
type: grep
pattern: "(useMemo|useCallback|memo|computed|\\$:)"
glob: "**/*.{ts,tsx,js,jsx,vue,svelte}"
verbosity: files_only
这将揭示:
使用 precision_read 配合符号提取来理解组件导出。
precision_read:
files:
- path: "src/components/Button/index.tsx" # 示例组件
extract: symbols
symbol_filter: ["function", "class", "interface", "type"]
verbosity: standard
这将揭示:
阅读 2-3 个结构良好的组件以了解实现模式。
precision_read:
files:
- path: "src/components/Button/Button.tsx"
extract: content
- path: "src/components/Form/Form.tsx"
extract: content
output:
max_per_item: 100
verbosity: standard
查阅 references/component-patterns.md 中的组织决策树。
常见模式:
决策因素:
详见 references/component-patterns.md 中的详细比较。
模式选择指南:
| 模式 | 适用场景 | 框架支持 |
|---|---|---|
| 子属性 | 简单的包装组件 | React、Vue(插槽)、Svelte |
| 渲染属性 | 动态渲染逻辑 | React(旧版) |
| 复合组件 | 相关组件共享状态 | React、Vue、Svelte |
| 高阶组件 | 横切关注点 | React(旧版) |
| 钩子/组合式函数 | 逻辑复用 | React、Vue 3、Svelte |
| 插槽 | 基于模板的组合 | Vue、Svelte |
现代推荐:
组件级状态:
应用级状态:
详见 references/component-patterns.md 中的状态管理决策树。
从属性的 TypeScript 接口开始。
React 示例:
import { ReactNode } from 'react';
interface ButtonProps {
/** 按钮内容 */
children: ReactNode;
/** 视觉样式变体 */
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
/** 尺寸预设 */
size?: 'sm' | 'md' | 'lg';
/** 加载状态 */
isLoading?: boolean;
/** 禁用状态 */
disabled?: boolean;
/** 点击处理函数 */
onClick?: () => void;
/** 额外的 CSS 类名 */
className?: string;
}
最佳实践:
? 明确表示可选属性any 类型Vue 3 示例:
import { defineComponent, PropType } from 'vue';
export default defineComponent({
props: {
variant: {
type: String as PropType<'primary' | 'secondary' | 'ghost' | 'danger'>,
default: 'primary',
},
size: {
type: String as PropType<'sm' | 'md' | 'lg'>,
default: 'md',
},
isLoading: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
},
});
Svelte 示例:
// Button.svelte (Svelte 4)
<script lang="ts">
export let variant: 'primary' | 'secondary' | 'ghost' | 'danger' = 'primary';
export let size: 'sm' | 'md' | 'lg' = 'md';
export let isLoading = false;
export let disabled = false;
</script>
// Button.svelte (Svelte 5 - 使用 $props 符文)
<script lang="ts">
let { variant = 'primary', size = 'md', isLoading = false, disabled = false }: {
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
disabled?: boolean;
} = $props();
</script>
遵循特定框架的模式实现组件。
React 组合示例:
import { forwardRef } from 'react';
import { cn } from '@/lib/utils';
import { Spinner } from './Spinner';
const buttonVariants = {
variant: {
primary: 'bg-blue-600 hover:bg-blue-700 text-white',
secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-900',
ghost: 'hover:bg-gray-100 text-gray-700',
danger: 'bg-red-600 hover:bg-red-700 text-white',
},
size: {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
},
};
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
children,
variant = 'primary',
size = 'md',
isLoading = false,
disabled = false,
className,
...props
},
ref
) => {
return (
<button
ref={ref}
className={cn(
'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',
buttonVariants.variant[variant],
buttonVariants.size[size],
className
)}
disabled={disabled || isLoading}
aria-busy={isLoading}
{...props}
>
{isLoading ? (
<>
<Spinner className="mr-2 h-4 w-4" aria-hidden />
<span>Loading...</span>
</>
) : (
children
)}
</button>
);
}
);
Button.displayName = 'Button';
关键模式:
forwardRef 暴露 DOM 引用...props 扩展以获得灵活性创建具有适当文件结构的组件目录。
标准结构:
components/
Button/
Button.tsx # 组件实现
Button.test.tsx # 单元测试
Button.stories.tsx # Storybook 故事(如果使用)
index.tsx # 桶式导出
types.ts # 类型定义(如果复杂)
使用精准工具实现:
precision_write:
files:
- path: "src/components/Button/Button.tsx"
content: |
import { forwardRef } from 'react';
// ... [完整实现]
- path: "src/components/Button/index.tsx"
content: |
export { Button } from './Button';
export type { ButtonProps } from './Button';
- path: "src/components/Button/Button.test.tsx"
content: |
import { render, screen } from '@testing-library/react';
import { Button } from './Button';
// ... [测试用例]
verbosity: count_only
本地状态(仅限组件):
提升状态(父组件管理):
全局状态(应用级别):
React - 本地状态:
import { useState } from 'react';
interface SearchResult {
id: string;
title: string;
description: string;
}
function SearchInput() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<SearchResult[]>([]);
const handleSearch = async () => {
const data = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
setResults(await data.json());
};
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<button onClick={handleSearch}>Search</button>
</>
);
}
React - 提升状态:
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<SearchResult[]>([]);
return (
<>
<SearchInput query={query} onQueryChange={setQuery} />
<SearchResults results={results} />
</>
);
}
React - 派生状态:
interface Item {
id: string;
category: string;
name: string;
}
interface FilteredListProps {
items: Item[];
filter: string;
}
function FilteredList({ items, filter }: FilteredListProps) {
// 不要将过滤后的项存储在状态中 - 派生它们
const filteredItems = items.filter(item => item.category === filter);
return <ul>{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
使用 Context 处理深层嵌套的属性:
import { createContext, useContext } from 'react';
const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} });
interface ThemeProviderProps {
children: ReactNode;
}
export function ThemeProvider({ children }: ThemeProviderProps) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => setTheme(t => t === 'light' ? 'dark' : 'light');
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export const useTheme = () => useContext(ThemeContext);
使用分析工具检测性能问题。
mcp__plugin_goodvibes_frontend-engine__trace_component_state:
component_file: "src/components/Dashboard.tsx"
target_component: "Dashboard"
mcp__plugin_goodvibes_frontend-engine__analyze_render_triggers:
component_file: "src/components/ExpensiveList.tsx"
这将揭示:
记忆化昂贵的计算:
import { useMemo } from 'react';
interface DataItem {
id: string;
[key: string]: unknown;
}
interface DataTableProps {
data: DataItem[];
filters: Record<string, any>;
}
function DataTable({ data, filters }: DataTableProps) {
const filteredData = useMemo(() => {
return data.filter(item => matchesFilters(item, filters));
}, [data, filters]);
return <table>{/* 渲染 filteredData */}</table>;
}
记忆化回调函数:
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// 当 count 变化时防止 Child 重新渲染
const handleClick = useCallback(() => {
onClick();
}, []);
return <Child onClick={handleClick} />;
}
记忆化组件:
import { memo } from 'react';
interface ExpensiveChildProps {
data: DataItem[];
}
const ExpensiveChild = memo(function ExpensiveChild({ data }: ExpensiveChildProps) {
// 仅当 data 变化时重新渲染
return <div>{/* 复杂渲染 */}</div>;
});
对于大型列表,使用虚拟化库。
import { useVirtualizer } from '@tanstack/react-virtual';
interface VirtualListProps<T = unknown> {
items: T[];
}
function VirtualList({ items }: VirtualListProps) {
const parentRef = useRef(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
// 注意:此处内联样式对于虚拟化定位是可接受的
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.index}
style={{
position: 'absolute',
top: 0,
left: 0,
transform: `translateY(${virtualRow.start}px)`,
}}
>
{items[virtualRow.index].name}
</div>
))}
</div>
</div>
);
}
React 懒加载:
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
使用正确的 HTML 元素而不是 div。
// 不好
<div onClick={handleClick}>Click me</div>
// 好
<button onClick={handleClick}>Click me</button>
为屏幕阅读器添加 ARIA 标签。
interface DialogProps {
isOpen: boolean;
onClose: () => void;
children: ReactNode;
}
function Dialog({ isOpen, onClose, children }: DialogProps) {
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-description"
>
<h2 id="dialog-title">Dialog Title</h2>
<div id="dialog-description">{children}</div>
<button onClick={onClose} aria-label="Close dialog">
X
</button>
</div>
);
}
确保所有交互元素都可通过键盘访问。
function Dropdown() {
const [isOpen, setIsOpen] = useState(false);
const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
if (e.key === 'Escape') setIsOpen(false);
if (e.key === 'Enter' || e.key === ' ') setIsOpen(!isOpen);
};
return (
<div>
<button
onClick={() => setIsOpen(!isOpen)}
onKeyDown={handleKeyDown}
aria-expanded={isOpen}
aria-haspopup="true"
>
Menu
</button>
{isOpen && <div role="menu">{/* 菜单项 */}</div>}
</div>
);
}
使用前端分析工具检查可访问性。
mcp__plugin_goodvibes_frontend-engine__get_accessibility_tree:
component_file: "src/components/Dialog.tsx"
验证 TypeScript 编译。
precision_exec:
commands:
- cmd: "npm run typecheck"
expect:
exit_code: 0
verbosity: minimal
使用验证脚本确保质量。
bash plugins/goodvibes/skills/outcome/component-architecture/scripts/validate-components.sh .
完整的验证套件请参见 scripts/validate-components.sh。
如果使用 Storybook 或类似工具,运行视觉测试。
precision_exec:
commands:
- cmd: "npm run test:visual"
expect:
exit_code: 0
verbosity: minimal
不要:
any 类型要:
useCallback探索阶段:
discover: { queries: [components, patterns, state, styling], verbosity: files_only }
precision_read: { files: [example components], extract: symbols }
实现阶段:
precision_write: { files: [Component.tsx, index.tsx, types.ts], verbosity: count_only }
性能分析:
trace_component_state: { component_file: "src/...", target_component: "Name" }
analyze_render_triggers: { component_file: "src/..." }
可访问性检查:
get_accessibility_tree: { component_file: "src/..." }
验证阶段:
precision_exec: { commands: [{ cmd: "npm run typecheck" }] }
实现后:
bash scripts/validate-components.sh .
有关详细模式、框架比较和决策树,请参见 references/component-patterns.md。
考虑使用以下互补的 GoodVibes 技能:
每周安装次数
52
代码仓库
GitHub 星标数
6
首次出现
2026年2月17日
安全审计
安装于
opencode51
github-copilot51
codex51
kimi-cli51
amp51
gemini-cli51
scripts/
validate-components.sh
references/
component-patterns.md
This skill guides you through designing and implementing UI components using GoodVibes precision and analysis tools. Use this workflow when building React, Vue, or Svelte components with proper composition, state management, and performance optimization.
Load this skill when:
Trigger phrases: "build component", "create UI", "component structure", "render optimization", "state lifting", "component composition".
Before building components, understand existing patterns in the codebase.
Use discover to find all component files and understand the organization pattern.
discover:
queries:
- id: react_components
type: glob
patterns: ["**/*.tsx", "**/*.jsx"]
- id: vue_components
type: glob
patterns: ["**/*.vue"]
- id: svelte_components
type: glob
patterns: ["**/*.svelte"]
verbosity: files_only
What this reveals:
Use discover to find composition patterns, state management, and styling approaches.
discover:
queries:
- id: composition_patterns
type: grep
pattern: "(children|render|slot|as\\s*=)"
glob: "**/*.{tsx,jsx,vue,svelte}"
- id: state_management
type: grep
pattern: "(useState|useReducer|reactive|writable|createSignal)"
glob: "**/*.{ts,tsx,js,jsx,vue,svelte}"
- id: styling_approach
type: grep
pattern: "(className|styled|css|tw`|@apply)"
glob: "**/*.{tsx,jsx,vue,svelte}"
- id: performance_hooks
type: grep
pattern: "(useMemo|useCallback|memo|computed|\\$:)"
glob: "**/*.{ts,tsx,js,jsx,vue,svelte}"
verbosity: files_only
What this reveals:
Use precision_read with symbol extraction to understand component exports.
precision_read:
files:
- path: "src/components/Button/index.tsx" # Example component
extract: symbols
symbol_filter: ["function", "class", "interface", "type"]
verbosity: standard
What this reveals:
Read 2-3 well-structured components to understand implementation patterns.
precision_read:
files:
- path: "src/components/Button/Button.tsx"
extract: content
- path: "src/components/Form/Form.tsx"
extract: content
output:
max_per_item: 100
verbosity: standard
Consult references/component-patterns.md for the organization decision tree.
Common patterns:
Decision factors:
See references/component-patterns.md for detailed comparison.
Pattern selection guide:
| Pattern | Use When | Framework Support |
|---|---|---|
| Children props | Simple wrapper components | React, Vue (slots), Svelte |
| Render props | Dynamic rendering logic | React (legacy) |
| Compound components | Related components share state | React, Vue, Svelte |
| Higher-Order Components | Cross-cutting concerns | React (legacy) |
| Hooks/Composables | Logic reuse | React, Vue 3, Svelte |
| Slots | Template-based composition | Vue, Svelte |
Modern recommendation:
Component-level state:
Application-level state:
See references/component-patterns.md for state management decision tree.
Start with TypeScript interfaces for props.
React Example:
import { ReactNode } from 'react';
interface ButtonProps {
/** Button content */
children: ReactNode;
/** Visual style variant */
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
/** Size preset */
size?: 'sm' | 'md' | 'lg';
/** Loading state */
isLoading?: boolean;
/** Disabled state */
disabled?: boolean;
/** Click handler */
onClick?: () => void;
/** Additional CSS classes */
className?: string;
}
Best practices:
?any typesVue 3 Example:
import { defineComponent, PropType } from 'vue';
export default defineComponent({
props: {
variant: {
type: String as PropType<'primary' | 'secondary' | 'ghost' | 'danger'>,
default: 'primary',
},
size: {
type: String as PropType<'sm' | 'md' | 'lg'>,
default: 'md',
},
isLoading: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
},
});
Svelte Example:
// Button.svelte (Svelte 4)
<script lang="ts">
export let variant: 'primary' | 'secondary' | 'ghost' | 'danger' = 'primary';
export let size: 'sm' | 'md' | 'lg' = 'md';
export let isLoading = false;
export let disabled = false;
</script>
// Button.svelte (Svelte 5 - using $props rune)
<script lang="ts">
let { variant = 'primary', size = 'md', isLoading = false, disabled = false }: {
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
disabled?: boolean;
} = $props();
</script>
Follow framework-specific patterns for component implementation.
React with Composition:
import { forwardRef } from 'react';
import { cn } from '@/lib/utils';
import { Spinner } from './Spinner';
const buttonVariants = {
variant: {
primary: 'bg-blue-600 hover:bg-blue-700 text-white',
secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-900',
ghost: 'hover:bg-gray-100 text-gray-700',
danger: 'bg-red-600 hover:bg-red-700 text-white',
},
size: {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
},
};
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
children,
variant = 'primary',
size = 'md',
isLoading = false,
disabled = false,
className,
...props
},
ref
) => {
return (
<button
ref={ref}
className={cn(
'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',
buttonVariants.variant[variant],
buttonVariants.size[size],
className
)}
disabled={disabled || isLoading}
aria-busy={isLoading}
{...props}
>
{isLoading ? (
<>
<Spinner className="mr-2 h-4 w-4" aria-hidden />
<span>Loading...</span>
</>
) : (
children
)}
</button>
);
}
);
Button.displayName = 'Button';
Key patterns:
forwardRef to expose DOM ref...props for flexibilityCreate component directory with proper file structure.
Standard structure:
components/
Button/
Button.tsx # Component implementation
Button.test.tsx # Unit tests
Button.stories.tsx # Storybook stories (if using)
index.tsx # Barrel export
types.ts # Type definitions (if complex)
Implementation with precision tools:
precision_write:
files:
- path: "src/components/Button/Button.tsx"
content: |
import { forwardRef } from 'react';
// ... [full implementation]
- path: "src/components/Button/index.tsx"
content: |
export { Button } from './Button';
export type { ButtonProps } from './Button';
- path: "src/components/Button/Button.test.tsx"
content: |
import { render, screen } from '@testing-library/react';
import { Button } from './Button';
// ... [test cases]
verbosity: count_only
Local state (component-only):
Lifted state (parent manages):
Global state (app-level):
React - Local State:
import { useState } from 'react';
interface SearchResult {
id: string;
title: string;
description: string;
}
function SearchInput() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<SearchResult[]>([]);
const handleSearch = async () => {
const data = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
setResults(await data.json());
};
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<button onClick={handleSearch}>Search</button>
</>
);
}
React - Lifted State:
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<SearchResult[]>([]);
return (
<>
<SearchInput query={query} onQueryChange={setQuery} />
<SearchResults results={results} />
</>
);
}
React - Derived State:
interface Item {
id: string;
category: string;
name: string;
}
interface FilteredListProps {
items: Item[];
filter: string;
}
function FilteredList({ items, filter }: FilteredListProps) {
// Don't store filtered items in state - derive them
const filteredItems = items.filter(item => item.category === filter);
return <ul>{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
Use Context for deeply nested props:
import { createContext, useContext } from 'react';
const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} });
interface ThemeProviderProps {
children: ReactNode;
}
export function ThemeProvider({ children }: ThemeProviderProps) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => setTheme(t => t === 'light' ? 'dark' : 'light');
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export const useTheme = () => useContext(ThemeContext);
Use analysis tools to detect performance problems.
mcp__plugin_goodvibes_frontend-engine__trace_component_state:
component_file: "src/components/Dashboard.tsx"
target_component: "Dashboard"
mcp__plugin_goodvibes_frontend-engine__analyze_render_triggers:
component_file: "src/components/ExpensiveList.tsx"
What these reveal:
Memoize expensive calculations:
import { useMemo } from 'react';
interface DataItem {
id: string;
[key: string]: unknown;
}
interface DataTableProps {
data: DataItem[];
filters: Record<string, any>;
}
function DataTable({ data, filters }: DataTableProps) {
const filteredData = useMemo(() => {
return data.filter(item => matchesFilters(item, filters));
}, [data, filters]);
return <table>{/* render filteredData */}</table>;
}
Memoize callback functions:
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// Prevent Child re-render when count changes
const handleClick = useCallback(() => {
onClick();
}, []);
return <Child onClick={handleClick} />;
}
Memoize components:
import { memo } from 'react';
interface ExpensiveChildProps {
data: DataItem[];
}
const ExpensiveChild = memo(function ExpensiveChild({ data }: ExpensiveChildProps) {
// Only re-renders if data changes
return <div>{/* complex rendering */}</div>;
});
For large lists, use virtualization libraries.
import { useVirtualizer } from '@tanstack/react-virtual';
interface VirtualListProps<T = unknown> {
items: T[];
}
function VirtualList({ items }: VirtualListProps) {
const parentRef = useRef(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
// Note: Inline styles acceptable here for virtualization positioning
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.index}
style={{
position: 'absolute',
top: 0,
left: 0,
transform: `translateY(${virtualRow.start}px)`,
}}
>
{items[virtualRow.index].name}
</div>
))}
</div>
</div>
);
}
React lazy loading:
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
Use proper HTML elements instead of divs.
// Bad
<div onClick={handleClick}>Click me</div>
// Good
<button onClick={handleClick}>Click me</button>
Add ARIA labels for screen readers.
interface DialogProps {
isOpen: boolean;
onClose: () => void;
children: ReactNode;
}
function Dialog({ isOpen, onClose, children }: DialogProps) {
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-description"
>
<h2 id="dialog-title">Dialog Title</h2>
<div id="dialog-description">{children}</div>
<button onClick={onClose} aria-label="Close dialog">
X
</button>
</div>
);
}
Ensure all interactive elements are keyboard-accessible.
function Dropdown() {
const [isOpen, setIsOpen] = useState(false);
const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
if (e.key === 'Escape') setIsOpen(false);
if (e.key === 'Enter' || e.key === ' ') setIsOpen(!isOpen);
};
return (
<div>
<button
onClick={() => setIsOpen(!isOpen)}
onKeyDown={handleKeyDown}
aria-expanded={isOpen}
aria-haspopup="true"
>
Menu
</button>
{isOpen && <div role="menu">{/* menu items */}</div>}
</div>
);
}
Use frontend analysis tools to check accessibility.
mcp__plugin_goodvibes_frontend-engine__get_accessibility_tree:
component_file: "src/components/Dialog.tsx"
Verify TypeScript compilation.
precision_exec:
commands:
- cmd: "npm run typecheck"
expect:
exit_code: 0
verbosity: minimal
Use the validation script to ensure quality.
bash plugins/goodvibes/skills/outcome/component-architecture/scripts/validate-components.sh .
See scripts/validate-components.sh for the complete validation suite.
If using Storybook or similar, run visual tests.
precision_exec:
commands:
- cmd: "npm run test:visual"
expect:
exit_code: 0
verbosity: minimal
DON'T:
any types for component propsDO:
useCallbackDiscovery Phase:
discover: { queries: [components, patterns, state, styling], verbosity: files_only }
precision_read: { files: [example components], extract: symbols }
Implementation Phase:
precision_write: { files: [Component.tsx, index.tsx, types.ts], verbosity: count_only }
Performance Analysis:
trace_component_state: { component_file: "src/...", target_component: "Name" }
analyze_render_triggers: { component_file: "src/..." }
Accessibility Check:
get_accessibility_tree: { component_file: "src/..." }
Validation Phase:
precision_exec: { commands: [{ cmd: "npm run typecheck" }] }
Post-Implementation:
bash scripts/validate-components.sh .
For detailed patterns, framework comparisons, and decision trees, see references/component-patterns.md.
Consider using these complementary GoodVibes skills:
Weekly Installs
52
Repository
GitHub Stars
6
First Seen
Feb 17, 2026
Security Audits
Gen Agent Trust HubWarnSocketPassSnykPass
Installed on
opencode51
github-copilot51
codex51
kimi-cli51
amp51
gemini-cli51
impeccable:创建独特生产级前端界面,告别AI垃圾美学
7,500 周安装