| name | create-component |
| description | Creates a new UI component with implementation, index, tests, and Storybook stories following PageZERO conventions. Use when adding a UI component or when the user asks to scaffold a component in packages/ui or apps. |
Create Component
Create a new UI component following the PageZERO component structure pattern.
Gather Information
Ask the user in a single question:
What component would you like to create and where should it go?
- Purpose: What should it do/display?
- Location:
packages/ui/ (reusable) or apps/{feature}/components/ (feature-specific)
Instructions
After gathering the information, create the full component structure:
-
Determine the location:
- UI components go in
packages/ui/{component-name}/
- Feature components go in
apps/{feature}/components/{component-name}/
- Ask if unclear
-
Create 4 files following the naming convention {component-name}.tsx:
{component-name}.tsx - Component Implementation
import * as React from "react"
import { cn } from "@/ui/utils"
interface {ComponentName}Props {
className?: string
children?: React.ReactNode
}
function {ComponentName}({ className, children }: {ComponentName}Props) {
return (
<div className={cn("", className)}>
{children}
</div>
)
}
export { {ComponentName}, type {ComponentName}Props }
index.ts - Public Exports
export * from "./{component-name}"
{component-name}.test.tsx - Unit Tests
import { render, screen } from "@testing-library/react"
import { describe, expect, it } from "vitest"
import { {ComponentName} } from "./{component-name}"
describe("<{ComponentName} />", () => {
it("renders", () => {
render(<{ComponentName}>Test content</{ComponentName}>)
expect(screen.getByText("Test content")).toBeInTheDocument()
})
})
{component-name}.stories.tsx - Storybook Stories
import type { Meta, StoryObj } from "@storybook/tanstack-react"
import { {ComponentName} } from "./{component-name}"
const meta = {
title: "{StoryTitle}/{ComponentName}",
component: {ComponentName},
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof {ComponentName}>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
children: "{ComponentName}",
},
}
Naming Conventions
- File names: Use kebab-case (e.g.,
checkout-button.tsx)
- Component names: Use PascalCase (e.g.,
CheckoutButton)
- Story titles:
- UI components:
"Packages/UI/{ComponentName}"
- Feature components:
"Apps/{FeatureName}/{ComponentName}"
Variants (if needed)
For components with variants, use cva (class-variance-authority):
import { cva, type VariantProps } from "class-variance-authority"
const {componentName}Variants = cva("base-classes", {
variants: {
variant: {
default: "default-classes",
secondary: "secondary-classes",
},
size: {
default: "size-default",
sm: "size-sm",
lg: "size-lg",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
})
type {ComponentName}Props = React.ComponentProps<"div"> &
VariantProps<typeof {componentName}Variants>
After Creation
- Run
bun run check:fix to format the new files (use required_permissions: ["all"] to run outside sandbox)
- Run
bun run test to verify tests pass
- Provide user link to relevant Storybook story