| name | create-component |
| description | Creates a new shared UI component using shadcn/ui with the project's theme. Handles shadcn setup verification, theme token mapping, and generates production-ready components following project conventions. |
| argument-hint | [ComponentName] [--variant button|input|dialog|card|table|form|custom] |
| allowed-tools | Bash, Read, Write, Edit, Glob, Grep |
| license | proprietary |
| compatibility | Requires Node.js, yarn, and a Next.js project with shadcn/ui and Tailwind CSS configured. |
| metadata | {"author":"tonehq","version":"1.1.0","category":"components","tags":"shadcn, tailwind, ui, components, react, nextjs, theme, clsx, cva"} |
You are a Senior Frontend Engineer creating shared UI components for a React + Next.js (App Router) codebase.
Your job is to produce high-quality, accessible, theme-consistent components using shadcn/ui and Tailwind CSS that integrate with the existing project theme.
Step 1 — Parse arguments
FULL_ARGS=$ARGUMENTS
1a. Extract component name
If $ARGUMENTS contains a component name (PascalCase word), set COMPONENT_NAME=<name>.
If empty, ask the user what component they want to create.
1b. Extract --variant flag (optional)
If $ARGUMENTS contains --variant <type>:
- Extract the variant type:
button | input | dialog | card | table | form | custom
- This hints which shadcn primitives to use as the base
Examples:
/create-component StatusBadge --variant button
→ COMPONENT_NAME=StatusBadge, VARIANT=button
/create-component SearchInput
→ COMPONENT_NAME=SearchInput, VARIANT=<auto-detect from name>
/create-component
→ Ask user for component name
1c. Auto-detect variant from name
If no --variant is provided, infer from the component name:
- Names containing
Button, Btn → button
- Names containing
Input, Field, Search → input
- Names containing
Dialog, Modal, Popup → dialog
- Names containing
Card, Panel, Tile → card
- Names containing
Table, Grid, List → table
- Names containing
Form → form
- Otherwise →
custom
Step 2 — Verify shadcn/ui setup
Check if shadcn/ui and Tailwind CSS are properly configured in the project.
2a. Check for required config files
ls components.json postcss.config.mjs 2>/dev/null
2b. Check for shadcn component directory
ls src/components/ui/ 2>/dev/null
2c. Check package.json for required dependencies
grep -E '"tailwindcss"|"@tailwindcss"|"class-variance-authority"|"clsx"|"tailwind-merge"' package.json
2d. Check for the cn() utility and theme constants
ls src/lib/utils.ts src/lib/theme.ts 2>/dev/null
2e. If setup is missing — run first-time setup
If any of the above checks fail, read and follow the setup guide:
.claude/skills/create-component/references/setup-guide.md
IMPORTANT: Do NOT proceed to Step 3 until setup is verified. The setup guide handles:
- Installing Tailwind CSS v4 and dependencies (
tailwindcss, @tailwindcss/postcss)
- Installing className utilities (
clsx, tailwind-merge, class-variance-authority)
- Creating the
cn() utility in src/lib/utils.ts (clsx + tailwind-merge)
- Initializing shadcn/ui with the project's theme colors
- Configuring CSS variables that match the project's design tokens
- Updating
globals.css with theme tokens
After running setup, re-verify all checks in 2a–2d pass.
Step 3 — Read reference files
Read ALL reference files before generating any component. Do not skip any.
.claude/skills/create-component/references/theme-mapping.md
.claude/skills/create-component/references/component-patterns.md
.claude/skills/create-component/references/setup-guide.md (if not already read in Step 2)
Also read the project theme files to ensure consistency:
src/constants/theme.ts — centralized theme constants (semantic color aliases, component presets)
src/app/globals.css — Tailwind theme tokens and CSS variables (source of truth for color values)
Step 4 — Check for existing components
Read the shared components reference first (reduces token usage vs reading all files):
frontend/docs/shared-components.md
This file lists all @/components/shared components with props and usage. Use it to avoid duplicating or overlapping with existing components.
Then check on disk:
ls src/components/ui/ 2>/dev/null
ls src/components/shared/ 2>/dev/null
Also use Glob to search for components with similar names:
src/components/**/*<ComponentName>*
If a similar component exists:
- Read it (or use the shared-components.md entry) to understand its API
- Ask the user if they want to extend the existing one or create a new shadcn-based replacement
- If replacing, note what props/features the existing component supports so the new one is feature-complete
Step 5 — Determine which shadcn primitives to install
Based on the variant and component requirements, determine which shadcn/ui primitives are needed.
Primitive mapping by variant:
| Variant | Primary Primitive(s) | Supporting Primitives |
|---|
button | button | badge, tooltip |
input | input, label | form, popover |
dialog | dialog | button, input, label |
card | card | badge, separator |
table | table | badge, button, dropdown-menu |
form | form, input, label | select, checkbox, switch, button |
custom | Determine from requirements | — |
Install missing primitives:
yarn dlx shadcn@latest add <primitive-name>
Check if each primitive already exists before installing:
ls src/components/ui/<primitive-name>.tsx 2>/dev/null
Step 6 — Generate the component
6a. Determine file location
All new shared components go in:
src/components/shared/<ComponentName>.tsx
If the component is a compound component (multiple files), create a directory:
src/components/shared/<component-name>/
├── index.ts # Barrel export
├── <ComponentName>.tsx # Main component
├── <SubComponent>.tsx # Sub-components (if needed)
└── types.ts # Shared types (if complex)
6b. Component structure template
Every component MUST follow this structure:
'use client';
import React from 'react';
import { cn } from '@/lib/utils';
import { presets, semantic } from '@/lib/theme';
interface <ComponentName>Props {
className?: string;
}
const <ComponentName>: React.FC<<ComponentName>Props> = ({
// Destructure props with defaults
className,
...props
}) => {
return (
<div className={cn('base-classes', className)} {...props}>
{/* Component content */}
</div>
);
};
<ComponentName>.displayName = '<ComponentName>';
export { <ComponentName> };
export default <ComponentName>;
6c. Apply theme tokens
Use the flat hex-based color palette defined as CSS variables in globals.css.
Reference theme-mapping.md for the full token list.
Color usage — flat palette (same approach as tone-test project):
className = 'bg-purple-500 text-white';
className = 'hover:bg-purple-600';
className = 'bg-purple-50';
className = 'text-purple-500';
className = 'bg-indigo-500 text-white';
className = 'bg-green-500 text-white';
className = 'bg-amber-500 text-white';
className = 'bg-red-500 text-white';
className = 'text-gray-800';
className = 'text-gray-500';
className = 'text-gray-300';
className = 'bg-gray-50';
className = 'bg-white';
className = 'bg-gray-100';
className = 'border-slate-200';
className = 'ring-purple-500';
Typography:
className = 'text-xs';
className = 'text-sm';
className = 'text-base';
className = 'text-lg';
className = 'text-xl';
className = 'font-normal';
className = 'font-medium';
className = 'font-semibold';
className = 'font-bold';
Border radius (from CSS variables):
className = 'rounded-sm';
className = 'rounded';
className = 'rounded-lg';
className = 'rounded-xl';
className = 'rounded-2xl';
Shadows (from CSS variables):
className = 'shadow-xs';
className = 'shadow-sm';
className = 'shadow-md';
className = 'shadow-lg';
Spacing follows Tailwind defaults (4px base unit):
className = 'p-2';
className = 'px-4';
className = 'gap-2';
className = 'h-[42px]';
6d. Styling rules
- Use
cn() for ALL className composition — combines clsx + tailwind-merge
- Use Tailwind utility classes — not inline styles
- Use CSS variables for theme colors — not hardcoded hex values
- Support
className prop on every component — allows consumer overrides
- Use
cva (class-variance-authority) for components with multiple variants
- No CSS-in-JS — the project is Tailwind/shadcn only; do not introduce
@emotion, styled-components, or @mui/*
6d-1. cn() usage patterns (MUST follow)
The cn() function (src/lib/utils.ts) chains clsx + tailwind-merge:
- clsx: accepts strings, objects, arrays, removes falsy values
- tailwind-merge: resolves Tailwind class conflicts (last class wins)
import { cn } from '@/lib/utils';
cn('flex items-center gap-2');
cn('base-classes', {
'bg-primary text-white': variant === 'primary',
'border border-slate-200': variant === 'outline',
'opacity-50 cursor-not-allowed': disabled,
});
cn('rounded border', loading && 'animate-pulse', error && 'border-destructive');
cn('base', isOpen ? 'rotate-180' : 'rotate-0');
cn(buttonVariants({ variant, size }), className);
cn('bg-primary text-white rounded', className);
Anti-patterns (NEVER use):
className={`base ${isActive ? 'active' : ''} ${className}`}
className={[base, isActive && 'active'].filter(Boolean).join(' ')}
cn(className, 'bg-primary')
className={cn('bg-primary', isActive && 'text-white', className)}
For the full pattern reference, see component-patterns.md section 1.
6e. Variant pattern with CVA
For components with multiple visual variants, use class-variance-authority:
import { cva, type VariantProps } from 'class-variance-authority';
const componentVariants = cva(
'inline-flex items-center justify-center rounded font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-purple-500 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
primary: 'bg-purple-500 text-white hover:bg-purple-600',
secondary: 'bg-indigo-500 text-white hover:bg-indigo-600',
outline: 'border border-slate-200 bg-white text-gray-800 hover:bg-gray-100',
ghost: 'text-gray-800 hover:bg-gray-100',
destructive: 'bg-red-500 text-white hover:bg-red-600',
link: 'text-purple-500 underline-offset-2 hover:underline',
},
size: {
sm: 'h-8 px-3 text-sm',
md: 'h-[42px] px-4 text-base',
lg: 'h-12 px-6 text-lg',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
},
);
interface ComponentProps extends VariantProps<typeof componentVariants> {
className?: string;
}
6f. Icon usage
Use lucide-react for icons (already installed in the project):
import { Search, Plus, X, ChevronDown, Eye, EyeOff } from 'lucide-react';
<Search className="h-4 w-4" />
<Plus className="h-5 w-5" />
6g. Loading state pattern
import { Loader2 } from 'lucide-react';
interface Props {
loading?: boolean;
disabled?: boolean;
}
<Button disabled={disabled || loading}>
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{loading ? 'Loading...' : children}
</Button>
6h. ForwardRef pattern (for components that need DOM access)
const ComponentName = React.forwardRef<HTMLDivElement, ComponentNameProps>(
({ className, ...props }, ref) => {
return (
<div ref={ref} className={cn('base-classes', className)} {...props} />
);
}
);
ComponentName.displayName = 'ComponentName';
Step 7 — Write the component file
Write the complete component file to disk. Ensure:
- Valid TypeScript syntax with no
any types
- Correct imports (shadcn primitives from
@/components/ui/, cn from @/lib/utils, theme from @/lib/theme)
'use client' directive at the top
- Both named and default exports
displayName set on the component
className prop accepted for consumer overrides
- Use
presets from @/lib/theme for standard patterns (buttons, inputs, cards, badges, text)
- Use
semantic from @/lib/theme for intent-based colors — never hardcode class strings that exist in presets
- Accessible by default (proper ARIA attributes, keyboard navigation)
Step 7b — Update shared components docs
After writing the component file, update the shared components reference so future reads (and this skill) can rely on one doc instead of many files.
- Open
frontend/docs/shared-components.md.
- Add a new section for the new component (same format as existing sections):
- Component name as
## <ComponentName>
- One-line description.
- Props table: Prop | Type | Default | Description.
- Short Example code block (basic + one with variants if applicable).
- Add the component to the Exports from
@/components/shared list at the bottom (and any new types).
- If the component is added to
src/components/shared/index.tsx, the exports list in the doc should match.
This keeps shared-components.md as the single source of truth and reduces token usage when understanding or creating shared UI.
Step 8 — Create usage examples
After writing the component, output a usage example:
## Usage
### Basic
\`\`\`tsx
import { ComponentName } from '@/components/shared/ComponentName';
<ComponentName prop="value" />
\`\`\`
### With variants
\`\`\`tsx
<ComponentName variant="primary" size="lg" />
<ComponentName variant="outline" size="sm" />
\`\`\`
### With custom classes
\`\`\`tsx
<ComponentName className="mt-4 w-full" />
\`\`\`
Step 9 — Verify the component
9a. Check TypeScript compilation
yarn tsc --noEmit --pretty 2>&1 | head -30
If there are type errors in the new component, fix them immediately.
9b. Check imports resolve
ls src/components/ui/*.tsx 2>/dev/null
ls src/lib/utils.ts 2>/dev/null
ls src/lib/theme.ts 2>/dev/null
Step 10 — Output summary
Confirm Step 7b is done: frontend/docs/shared-components.md has been updated with the new component’s section and exports list.
# Component Created
**Component**: <ComponentName>
**File**: `src/components/shared/<ComponentName>.tsx`
**Docs updated**: `frontend/docs/shared-components.md`
**Base primitives**: <list of shadcn primitives used>
**Variant**: <variant type>
---
## Props API
| Prop | Type | Default | Description |
| ---- | ---- | ------- | ----------- |
| ... | ... | ... | ... |
---
## Theme Integration
- Colors: <which theme tokens are used>
- Typography: <font sizes/weights>
- Border radius: <radius tokens>
- Spacing: <relevant spacing>
---
## Shadcn Primitives Used
| Primitive | Installed | File |
| --------- | --------- | ------------------------------ |
| button | Yes/New | `src/components/ui/button.tsx` |
| ... | ... | ... |
---
## Usage Examples
<examples from Step 8>
---
## Next Steps
- [ ] Import and use in your page/feature component
- [ ] Customize variants as needed
- [ ] Add to Storybook (if applicable)
Step 11 — Ask for follow-up
After presenting the summary, ask:
How would you like to proceed?
- Create another variant of this component
- Create a different component
- Modify props or behavior
- Done
Do NOT make additional changes until the user explicitly chooses an option.
Important conventions
- File naming: PascalCase for component files (
StatusBadge.tsx), kebab-case for directories
- Shared components docs: After creating or changing any shared component, update
frontend/docs/shared-components.md (props table + example + exports list). This doc is the single reference for shared UI and reduces token usage.
- Exports: Both named and default export on every component
- TypeScript: Use
interface over type for props. Use React.FC<Props> pattern
- shadcn/Tailwind only: All shared components use shadcn/Tailwind exclusively. Do not introduce
@mui/*, @emotion/*, or styled-components
- Theme imports: Always import
presets and semantic from @/constants/theme — use presets for standard patterns (buttons, inputs, cards, badges) and semantic for intent-based one-off styling
- Theme consistency: Always use semantic Tailwind classes that reference CSS variables (
bg-primary, text-foreground, border-border) — never hardcode hex colors
- Accessibility: Every interactive element must be keyboard-accessible with proper ARIA attributes
- Icons: Use
lucide-react for everything; src/components/icons/ is reserved for brand marks
- Responsive: Use Tailwind responsive prefixes (
sm:, md:, lg:) for responsive behavior
- Dark mode ready: All colors flow through CSS variables in
globals.css — to add dark mode, add a .dark variant in globals and the entire app updates automatically