Claude-skill-registry component-scaffold-generator
Generates clean React/Vue component skeletons with TypeScript types, prop variants, styling hooks, test files, Storybook stories, and usage documentation. Use when users request "create a component", "scaffold component", "new React component", or "generate component boilerplate".
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/component-scaffold-generator" ~/.claude/skills/majiayu000-claude-skill-registry-component-scaffold-generator && rm -rf "$T"
manifest:
skills/data/component-scaffold-generator/SKILL.mdsource content
Component Scaffold Generator
Generate production-ready component skeletons with types, variants, tests, and documentation.
Core Workflow
- Gather requirements: Component name, framework (React/Vue), props needed
- Choose pattern: Determine if functional, compound, or polymorphic component
- Generate component: Create main component file with TypeScript types
- Add variants: Include common variants (size, color, state)
- Setup styling: Add styling approach (Tailwind, CSS Modules, styled-components)
- Create tests: Generate test file with basic coverage
- Add story: Create Storybook story with examples
- Document usage: Include JSDoc and usage examples
Component Patterns
Basic Functional Component (React)
// Button.tsx import { forwardRef } from "react"; import { cn } from "@/lib/utils"; export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { variant?: "primary" | "secondary" | "ghost" | "destructive"; size?: "sm" | "md" | "lg"; isLoading?: boolean; leftIcon?: React.ReactNode; rightIcon?: React.ReactNode; } /** * Button component with multiple variants and sizes * * @example * ```tsx * <Button variant="primary" size="md"> * Click me * </Button> * ``` */ export const Button = forwardRef<HTMLButtonElement, ButtonProps>( ( { variant = "primary", size = "md", isLoading = false, leftIcon, rightIcon, className, children, disabled, ...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 focus-visible:ring-offset-2", "disabled:pointer-events-none disabled:opacity-50", { // Variants "bg-blue-600 text-white hover:bg-blue-700": variant === "primary", "bg-gray-200 text-gray-900 hover:bg-gray-300": variant === "secondary", "hover:bg-gray-100": variant === "ghost", "bg-red-600 text-white hover:bg-red-700": variant === "destructive", // Sizes "h-8 px-3 text-sm": size === "sm", "h-10 px-4 text-base": size === "md", "h-12 px-6 text-lg": size === "lg", }, className )} disabled={disabled || isLoading} {...props} > {isLoading && ( <svg className="mr-2 h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" > <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" /> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" /> </svg> )} {leftIcon && <span className="mr-2">{leftIcon}</span>} {children} {rightIcon && <span className="ml-2">{rightIcon}</span>} </button> ); } ); Button.displayName = "Button";
Compound Component Pattern
// Card.tsx import { createContext, useContext } from "react"; interface CardContextValue { variant: "default" | "outlined" | "elevated"; } const CardContext = createContext<CardContextValue | undefined>(undefined); export interface CardProps extends React.HTMLAttributes<HTMLDivElement> { variant?: "default" | "outlined" | "elevated"; } export const Card = ({ variant = "default", className, children, ...props }: CardProps) => { return ( <CardContext.Provider value={{ variant }}> <div className={cn("rounded-lg", className)} {...props}> {children} </div> </CardContext.Provider> ); }; export const CardHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn("p-6", className)} {...props} /> ); export const CardTitle = ({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => ( <h3 className={cn("text-2xl font-semibold", className)} {...props} /> ); export const CardContent = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn("p-6 pt-0", className)} {...props} /> ); export const CardFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => ( <div className={cn("flex items-center p-6 pt-0", className)} {...props} /> );
Test File Template
// Button.test.tsx import { render, screen, fireEvent } from "@testing-library/react"; import { Button } from "./Button"; describe("Button", () => { it("renders children correctly", () => { render(<Button>Click me</Button>); expect( screen.getByRole("button", { name: /click me/i }) ).toBeInTheDocument(); }); it("applies variant classes correctly", () => { const { rerender } = render(<Button variant="primary">Test</Button>); expect(screen.getByRole("button")).toHaveClass("bg-blue-600"); rerender(<Button variant="secondary">Test</Button>); expect(screen.getByRole("button")).toHaveClass("bg-gray-200"); }); it("handles click events", () => { const handleClick = jest.fn(); render(<Button onClick={handleClick}>Click</Button>); fireEvent.click(screen.getByRole("button")); expect(handleClick).toHaveBeenCalledTimes(1); }); it("shows loading state", () => { render(<Button isLoading>Loading</Button>); expect(screen.getByRole("button")).toBeDisabled(); expect(screen.getByRole("button")).toContainHTML("svg"); }); it("renders with icons", () => { render( <Button leftIcon={<span data-testid="left">←</span>}>With Icon</Button> ); expect(screen.getByTestId("left")).toBeInTheDocument(); }); it("forwards ref correctly", () => { const ref = React.createRef<HTMLButtonElement>(); render(<Button ref={ref}>Test</Button>); expect(ref.current).toBeInstanceOf(HTMLButtonElement); }); });
Storybook Story Template
// Button.stories.tsx import type { Meta, StoryObj } from "@storybook/react"; import { Button } from "./Button"; const meta: Meta<typeof Button> = { title: "Components/Button", component: Button, tags: ["autodocs"], argTypes: { variant: { control: "select", options: ["primary", "secondary", "ghost", "destructive"], }, size: { control: "select", options: ["sm", "md", "lg"], }, isLoading: { control: "boolean", }, }, }; export default meta; type Story = StoryObj<typeof Button>; export const Primary: Story = { args: { variant: "primary", children: "Button", }, }; export const Secondary: Story = { args: { variant: "secondary", children: "Button", }, }; export const WithIcons: Story = { args: { children: "With Icons", leftIcon: "←", rightIcon: "→", }, }; export const Loading: Story = { args: { children: "Loading", isLoading: true, }, }; export const Sizes: Story = { render: () => ( <div className="flex gap-4 items-center"> <Button size="sm">Small</Button> <Button size="md">Medium</Button> <Button size="lg">Large</Button> </div> ), }; export const Variants: Story = { render: () => ( <div className="flex gap-4"> <Button variant="primary">Primary</Button> <Button variant="secondary">Secondary</Button> <Button variant="ghost">Ghost</Button> <Button variant="destructive">Destructive</Button> </div> ), };
Vue Component Pattern
<!-- Button.vue --> <script setup lang="ts"> import { computed } from "vue"; interface Props { variant?: "primary" | "secondary" | "ghost" | "destructive"; size?: "sm" | "md" | "lg"; isLoading?: boolean; disabled?: boolean; } const props = withDefaults(defineProps<Props>(), { variant: "primary", size: "md", isLoading: false, disabled: false, }); const emit = defineEmits<{ click: [event: MouseEvent]; }>(); const buttonClasses = computed(() => { return [ "inline-flex items-center justify-center rounded-md font-medium transition-colors", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2", "disabled:pointer-events-none disabled:opacity-50", // Variants { "bg-blue-600 text-white hover:bg-blue-700": props.variant === "primary", "bg-gray-200 text-gray-900 hover:bg-gray-300": props.variant === "secondary", "hover:bg-gray-100": props.variant === "ghost", "bg-red-600 text-white hover:bg-red-700": props.variant === "destructive", }, // Sizes { "h-8 px-3 text-sm": props.size === "sm", "h-10 px-4 text-base": props.size === "md", "h-12 px-6 text-lg": props.size === "lg", }, ]; }); const handleClick = (event: MouseEvent) => { if (!props.disabled && !props.isLoading) { emit("click", event); } }; </script> <template> <button :class="buttonClasses" :disabled="disabled || isLoading" @click="handleClick" > <svg v-if="isLoading" class="mr-2 h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" > <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" /> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" /> </svg> <slot /> </button> </template>
Index File (Barrel Export)
// index.ts export { Button } from "./Button"; export type { ButtonProps } from "./Button";
Usage Documentation Template
# Button Component A flexible button component with multiple variants, sizes, and states. ## Installation ```bash npm install @/components/ui/button ```
Usage
import { Button } from "@/components/ui/button"; function App() { return ( <Button variant="primary" size="md" onClick={() => console.log("Clicked!")}> Click me </Button> ); }
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | 'primary' | 'secondary' | 'ghost' | 'destructive' | 'primary' | Visual style variant |
| size | 'sm' | 'md' | 'lg' | 'md' | Button size |
| isLoading | boolean | false | Shows loading spinner |
| leftIcon | ReactNode | - | Icon before text |
| rightIcon | ReactNode | - | Icon after text |
| disabled | boolean | false | Disables the button |
Examples
With Icons
<Button leftIcon={<ChevronLeft />}>Back</Button>
Loading State
<Button isLoading>Saving...</Button>
Destructive Action
<Button variant="destructive" onClick={handleDelete}> Delete Account </Button>
Accessibility
- Keyboard navigable
- ARIA attributes included
- Focus visible styling
- Disabled state properly handled
## Component Structure Template
ComponentName/ ├── ComponentName.tsx # Main component ├── ComponentName.test.tsx # Tests ├── ComponentName.stories.tsx # Storybook ├── types.ts # TypeScript types (if complex) ├── styles.module.css # CSS Modules (if used) └── index.ts # Barrel export
## Common Component Types ### UI Primitives - Button, Input, Select, Checkbox, Radio - Typography (Text, Heading, Label) - Icons, Avatar, Badge ### Layout Components - Container, Stack, Grid, Flex - Card, Panel, Section - Divider, Spacer ### Data Display - Table, List, DataGrid - Tooltip, Popover, Dropdown - Tabs, Accordion, Collapse ### Feedback - Alert, Toast, Modal - Progress, Spinner, Skeleton - EmptyState, ErrorBoundary ### Forms - FormField, FormGroup, FormLabel - FileUpload, DatePicker, RangePicker - SearchInput, TagInput ## Best Practices 1. **Forward refs**: Use `forwardRef` for DOM access 2. **Type props properly**: Extend native HTML element props 3. **Use composition**: Compound components when appropriate 4. **Accessibility first**: ARIA attributes, keyboard nav 5. **Variants system**: Size, color, state variants 6. **Loading states**: Handle async operations 7. **Error states**: Validate and show errors 8. **Test coverage**: At least 80% coverage 9. **Document usage**: JSDoc and README examples 10. **Export types**: Make TypeScript types available ## Styling Approaches ### Tailwind CSS (Recommended) - Use `cn()` utility for conditional classes - Define variants with object syntax - Responsive utilities built-in ### CSS Modules - Scoped styles by default - Type-safe with TypeScript - Better for complex animations ### Styled Components - CSS-in-JS with props - Dynamic theming support - Runtime styling ## Output Checklist Every generated component should include: - [ ] Main component file with TypeScript types - [ ] Prop interface with JSDoc comments - [ ] Multiple variants (size, color, state) - [ ] Accessibility attributes - [ ] Test file with key scenarios - [ ] Storybook story with examples - [ ] Barrel export (index.ts) - [ ] Usage documentation - [ ] Error handling - [ ] Loading states (if applicable)