| name | create-react-modlet |
| description | Create React components, hooks, or utilities following the modlet pattern. Use when creating any component in packages/client/src/components/. Modlets are self-contained folders with index.ts, implementation, tests, stories (for visual), and optional types. |
Skill: Creating React Modlets
This skill teaches how to create React components, hooks, and utilities following the modlet pattern in this project.
What Is a Modlet?
A modlet is a self-contained folder that houses everything related to a specific module. Each modlet includes its implementation, tests, stories (for visual components), and types—all in one place.
When to Use This Skill
Use this skill when:
- Creating any new React component
- Creating a custom hook
- Creating a utility/helper function
- Breaking down a complex component into sub-components
Modlet Locations
Components are organized in packages/client/src/components/:
packages/client/src/components/
├── ui/ # Shadcn UI components (DO NOT modify manually)
├── common/ # Shared reusable components
├── inline-edit/ # Inline editing components (grouping folder)
├── Header/ # Feature component
├── CaseList/ # Feature component
├── CaseDetails/ # Feature component
└── MenuList/ # Feature component
Naming Conventions
- PascalCase for component folders:
EditableTitle/, Header/, Button/
- kebab-case for grouping folders:
inline-edit/, common/
- lowercase for standard folders:
components/, hooks/, helpers/
Modlet Types
Visual Modlet (React Component)
ComponentName/
├── index.ts # Re-export entry point (required)
├── ComponentName.tsx # Component implementation (required)
├── ComponentName.test.tsx # Tests (required)
├── ComponentName.stories.tsx # Storybook story (required)
└── types.ts # Custom types (optional)
Non-Visual Modlet (Hook/Utility)
useMyHook/
├── index.ts # Re-export entry point (required)
├── useMyHook.ts # Implementation (required)
├── useMyHook.test.ts # Tests (required)
└── types.ts # Custom types (optional)
Core Rules
- Folder naming: Name the modlet folder after its main export (e.g.,
/Button exports Button)
- index.ts only re-exports: Never define logic in
index.ts, only re-export from within the modlet
- Tests are mandatory: Every modlet must have tests in
<modlet-name>.test.tsx
- Stories for visual modlets: All visual components must have Storybook stories
- Sub-organization: Additional components go in
/components subfolder, hooks in /hooks, helpers in /helpers - each as its own modlet
Creation Process
Step 1: Plan with Todos
Use manage_todo_list to track progress:
1. Create modlet folder
2. Add index.ts with re-exports
3. Add main component/hook file
4. Add test file
5. (Visual) Add story file
6. (If needed) Add types.ts
7. Verify tests pass
8. Verify TypeScript compiles
9. (Visual) Verify story renders
Step 2: Create Files
index.ts (Required)
export { ComponentName } from './ComponentName';
export type { ComponentNameProps } from './types';
ComponentName.tsx (Required)
import { cn } from '@/lib/utils';
import type { ComponentNameProps } from './types';
export function ComponentName({ className, ...props }: ComponentNameProps) {
return (
<div className={cn('component-styles', className)}>
{/* Implementation */}
</div>
);
}
ComponentName.test.tsx (Required)
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ComponentName } from './ComponentName';
describe('ComponentName', () => {
it('should render correctly', () => {
render(<ComponentName />);
expect(screen.getByText('expected text')).toBeInTheDocument();
});
it('should handle user interaction', async () => {
const user = userEvent.setup();
const handleClick = vi.fn();
render(<ComponentName onClick={handleClick} />);
await user.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalled();
});
});
ComponentName.stories.tsx (Required for Visual)
import type { Meta, StoryObj } from '@storybook/react';
import { ComponentName } from './ComponentName';
const meta: Meta<typeof ComponentName> = {
component: ComponentName,
title: 'Common/ComponentName',
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<typeof ComponentName>;
export const Default: Story = {
args: {
},
};
export const Variant: Story = {
args: {
},
};
types.ts (Optional)
export interface ComponentNameProps {
className?: string;
}
export type ComponentNameVariant = 'primary' | 'secondary';
Step 3: Verify
Run these commands from the project root:
npm run test
npm run typecheck
npm run storybook
Project-Specific Patterns
Import Aliases
Use the @/ alias for imports:
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { SomeComponent } from '@/components/common/SomeComponent';
Shadcn UI Components
Reuse components from @/components/ui/:
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardHeader, CardContent } from '@/components/ui/card';
Tailwind CSS
Use Tailwind classes for styling. Use cn() for conditional classes:
import { cn } from '@/lib/utils';
<div className={cn('base-classes', isActive && 'active-classes', className)} />
Story Decorators
When components need context providers:
import { BrowserRouter } from 'react-router-dom';
const meta: Meta<typeof ComponentName> = {
component: ComponentName,
decorators: [
(Story) => (
<BrowserRouter>
<Story />
</BrowserRouter>
),
],
};
Sub-Modlet Organization
For complex components with internal sub-components:
ComplexComponent/
├── index.ts
├── ComplexComponent.tsx
├── ComplexComponent.test.tsx
├── ComplexComponent.stories.tsx
├── types.ts
├── components/
│ ├── SubComponentA/
│ │ ├── index.ts
│ │ ├── SubComponentA.tsx
│ │ └── SubComponentA.test.tsx
│ └── SubComponentB/
│ ├── index.ts
│ ├── SubComponentB.tsx
│ └── SubComponentB.test.tsx
└── hooks/
└── useComponentLogic/
├── index.ts
├── useComponentLogic.ts
└── useComponentLogic.test.ts
Quality Checklist
Before completing any modlet:
Common Mistakes to Avoid
- ❌ Missing
index.ts file
- ❌ Defining logic directly in
index.ts instead of re-exporting
- ❌ Skipping tests
- ❌ Missing Storybook stories for visual components
- ❌ Not verifying tests pass after creation
- ❌ Folder name doesn't match main export name
Existing Examples
Reference these existing modlets for patterns:
Full modlet with types:
components/Header/ - Has index.ts, component, test, story, types
Modlet in grouping folder:
components/inline-edit/EditableText/ - Inline edit component
Common component:
components/common/ConfirmationDialog/ - Dialog component in common folder
Testing Notes
This project uses:
- Vitest for test runner
- @testing-library/react for component testing
- @testing-library/user-event for user interaction testing
- @testing-library/jest-dom for DOM matchers
Tests are configured via vitest.config.ts with jsdom environment.