web-component-design
Install this skill
npx skills add wshobson/agentsWorks across Claude Code, Cursor, Codex, Copilot & Antigravity
Web component design focuses on building modular UI units that prioritize extensibility and consistent state management. Instead of building monolithic pages, this skill emphasizes crafting standalone building blocks—such as compound components, controlled inputs, and slot-based wrappers—that allow developers to assemble interfaces declaratively. By applying structural patterns like render props or context-based item discovery, you separate the underlying logic from the visual presentation. This systematic approach reduces technical debt in large repositories by ensuring prop interfaces remain predictable and style encapsulation remains strict. Whether defining design tokens for a global system or creating highly flexible data-fetching wrappers, this skill ensures that individual interface parts remain decoupled from the host application, allowing them to scale across multiple projects or complex internal dashboards without creating tight coupling or repetitive code paths.
When to Use This Skill
- •Developing internal design systems or shared component libraries
- •Refactoring bloated legacy class components into modular functions
- •Building flexible navigation or accordion systems with sub-components
- •Creating UI parts that handle complex data orchestration internally
How to Invoke This Skill
Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:
- “Refactor this list into a compound component pattern
- “Create a reusable button variant using class-variance-authority
- “Design a controlled input wrapper for our form library
- “Suggest an API interface for a complex card component
- “Convert this logic into a render prop pattern
Pro Tips
- 💡Always prioritize accessibility from the outset; well-designed components are inherently inclusive and usable for everyone.
- 💡When designing component APIs, aim for simplicity and predictability, mirroring native HTML elements or established patterns where appropriate for ease of use.
- 💡Embrace tools like Storybook for isolated development, visual testing, and comprehensive documentation of your components, ensuring consistency and accelerated development.
What this skill does
- •Construction of compound component patterns for shared state
- •Implementation of prop-driven visual variants using utility-first styling
- •Establishment of strict TypeScript interfaces for component APIs
- •Application of render prop patterns to offload display logic
- •Normalization of component behavior through consistent prop naming
When not to use it
- ✕Simple prototypes where static HTML and minimal CSS suffice
- ✕Single-use UI blocks that do not require state or lifecycle logic
Example workflow
- Define the component prop interface using TypeScript
- Create style variants using a library like CVA or Tailwind
- Implement the base component with structural slots or children
- Wrap child elements in context if sharing state is required
- Add documentation and default exports for the library consumer
Prerequisites
- –Proficiency in TypeScript
- –Basic knowledge of UI component frameworks like React, Vue, or Svelte
Pitfalls & limitations
- !Over-engineering simple components that do not need context
- !Creating overly restrictive prop APIs that break under edge cases
- !Forgetting to forward refs, breaking parent interactions
FAQ
How it compares
Generic prompts often produce unstyled or inconsistent code; this skill provides a structured architecture that enforces type safety, state management, and clear API boundaries.
📄 Full skill instructions — original source: wshobson/agents
Build reusable, maintainable UI components using modern frameworks with clean composition patterns and styling approaches.
## When to Use This Skill
- Designing reusable component libraries or design systems
- Implementing complex component composition patterns
- Choosing and applying CSS-in-JS solutions
- Building accessible, responsive UI components
- Creating consistent component APIs across a codebase
- Refactoring legacy components into modern patterns
- Implementing compound components or render props
## Core Concepts
### 1. Component Composition Patterns
**Compound Components**: Related components that work together
// Usage
<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>**Render Props**: Delegate rendering to parent
<DataFetcher url="/api/users">
{({ data, loading, error }) =>
loading ? <Spinner /> : <UserList users={data} />
}
</DataFetcher>**Slots (Vue/Svelte)**: Named content injection points
<template>
<Card>
<template #header>Title</template>
<template #content>Body text</template>
<template #footer><Button>Action</Button></template>
</Card>
</template>### 2. CSS-in-JS Approaches
| Solution | Approach | Best For |
| --------------------- | ---------------------- | --------------------------------- |
| **Tailwind CSS** | Utility classes | Rapid prototyping, design systems |
| **CSS Modules** | Scoped CSS files | Existing CSS, gradual adoption |
| **styled-components** | Template literals | React, dynamic styling |
| **Emotion** | Object/template styles | Flexible, SSR-friendly |
| **Vanilla Extract** | Zero-runtime | Performance-critical apps |
### 3. Component API Design
interface ButtonProps {
variant?: "primary" | "secondary" | "ghost";
size?: "sm" | "md" | "lg";
isLoading?: boolean;
isDisabled?: boolean;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
children: React.ReactNode;
onClick?: () => void;
}**Principles**:
- Use semantic prop names (
isLoading vs loading)- Provide sensible defaults
- Support composition via
children- Allow style overrides via
className or style## Quick Start: React Component with Tailwind
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";## Framework Patterns
### React: Compound Components
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>
);
};### Vue 3: Composables
<script setup lang="ts">
import { ref, computed, provide, inject, type InjectionKey } from "vue";
interface TabsContext {
activeTab: Ref<string>;
setActive: (id: string) => void;
}
const TabsKey: InjectionKey<TabsContext> = Symbol("tabs");
// Parent component
const activeTab = ref("tab-1");
provide(TabsKey, {
activeTab,
setActive: (id: string) => {
activeTab.value = id;
},
});
// Child component usage
const tabs = inject(TabsKey);
const isActive = computed(() => tabs?.activeTab.value === props.id);
</script>### Svelte 5: Runes
<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>## Best Practices
1. **Single Responsibility**: Each component does one thing well
2. **Prop Drilling Prevention**: Use context for deeply nested data
3. **Accessible by Default**: Include ARIA attributes, keyboard support
4. **Controlled vs Uncontrolled**: Support both patterns when appropriate
5. **Forward Refs**: Allow parent access to DOM nodes
6. **Memoization**: Use
React.memo, useMemo for expensive renders7. **Error Boundaries**: Wrap components that may fail
## Common Issues
- **Prop Explosion**: Too many props - consider composition instead
- **Style Conflicts**: Use scoped styles or CSS Modules
- **Re-render Cascades**: Profile with React DevTools, memo appropriately
- **Accessibility Gaps**: Test with screen readers and keyboard navigation
- **Bundle Size**: Tree-shake unused component variants
## Resources
- [React Component Patterns](https://reactpatterns.com/)
- [Vue Composition API Guide](https://vuejs.org/guide/reusability/composables.html)
- [Svelte Component Documentation](https://svelte.dev/docs/svelte-components)
- [Radix UI Primitives](https://www.radix-ui.com/primitives)
- [shadcn/ui Components](https://ui.shadcn.com/)
How to Use This Skill Unit
Option A: Project-Specific (Recommended)
- Click "Download" above
- In your project, create the directory:
.agent/skills/web-component-design/ - Save the file as
SKILL.md - The agent will automatically discover the skill based on its description.
Option B: Global Installation (All Agents)
Save the file to these locations to make it available across all projects:
- Claude Code:
~/.claude/skills/wshobson/agents/web-component-design/SKILL.md - Cursor:
~/.cursor/skills/wshobson/agents/web-component-design/SKILL.md - Antigravity:
~/.gemini/antigravity/skills/wshobson/agents/web-component-design/SKILL.md
🚀 Install with CLI:npx skills add wshobson/agents