nuxt-ui-tdd
NuxtUI TDD Component Development
Overview
Build Vue 3 components with NuxtUI following strict Test-Driven Development (TDD) methodology. This skill provides workflows, templates, and guardrails for creating well-tested, reusable UI components using the Atomic Design pattern.
When to Use This Skill
Use this skill when:
- Building new Vue components (atoms, molecules, organisms)
- Creating components that wrap NuxtUI base components (UButton, UInput, UFormField, etc.)
- Working with Storybook interaction tests
- Following TDD methodology with the TDD guard hook
- User requests involve "implement [ComponentName]", "create a component", "build with TDD"
Core Workflow
1. Determine Component Level
Before starting, identify the atomic design level. Consult references/naming-conventions.md for detailed guidance.
Quick Reference:
- Atoms: Single UI elements (Button, Input, Label, Icon)
- Molecules: 2-3 atoms combined (FormField, SearchBar, ArticleCard)
- Organisms: Complex sections (ArticleList, Navigation, ReaderView)
- Templates: Page layouts (DefaultLayout, AuthLayout)
Naming Patterns:
- Atoms:
{Element}.vue(e.g., Button.vue) - Molecules:
{Concept}{Type}.vue(e.g., FormField.vue, SearchBar.vue) - Organisms:
{Feature}{Component}.vue(e.g., ArticleList.vue)
2. Create the Test File First
Location: Same directory as component
Naming: {ComponentName}.stories.ts
Use the template from assets/story-template.ts as a starting point.
Critical TDD Rules:
- Create ONLY ONE test/story initially
- Use the "Default" story for basic rendering
- Include ONE focused assertion per story
- Run test to confirm it fails (RED phase)
Example First Test:
import type { Meta, StoryObj } from '@storybook/vue3';
import { expect, within } from '@storybook/test';
import SearchBar from './SearchBar.vue';
const meta = {
title: 'Molecules/SearchBar',
component: SearchBar,
tags: ['autodocs'],
argTypes: {
placeholder: {
control: 'text',
description: 'Placeholder text',
},
},
} satisfies Meta<typeof SearchBar>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
placeholder: 'Search articles...',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText(/search articles/i);
await expect(input).toBeInTheDocument();
},
};
3. Capture RED Phase Evidence
Run Storybook test-runner to verify the test fails:
pnpm test:storybook:run -- {ComponentName}
Save the failure output:
pnpm test:storybook:run -- SearchBar | tee /tmp/searchbar-red-phase.txt
Expected: Test fails because component doesn't exist.
4. Update test.json for TDD Guard
Manually update .claude/tdd-guard/data/test.json to record the RED phase:
{
"verificationMode": false,
"batchMode": false,
"testModules": [
{
"moduleId": "/Users/tk/Code/poche/apps/web/components/molecules/SearchBar.stories.ts",
"tests": [
{
"name": "Default",
"fullName": "Molecules/SearchBar › Default",
"state": "failed",
"note": "RED phase - component doesn't exist"
}
]
}
],
"redPhaseEvidence": {
"completed": true,
"command": "pnpm test:storybook -- SearchBar",
"exitCode": 1,
"timestamp": "2025-11-02T13:52:00.000Z",
"testSuitesFailed": 1,
"failureCount": 1,
"totalTests": 1,
"sampleErrors": [
"FAIL browser: chromium components/molecules/SearchBar.stories.ts",
"Failed to fetch dynamically imported module"
]
},
"unhandledErrors": [],
"reason": "failed"
}
This step is CRITICAL - the TDD guard will block implementation without RED phase evidence.
5. Implement Minimal Component
Location: apps/web/components/{level}/{ComponentName}.vue
Use assets/component-template.vue as a starting point.
Critical Implementation Rules:
- Write ONLY the code needed to pass the current test
- Do NOT add untested props or functionality
- Wrap appropriate NuxtUI base components
- Keep it simple and minimal
Example Minimal Implementation:
<script setup lang="ts">
defineProps<{
placeholder?: string;
}>();
</script>
<template>
<UInput
type="search"
:placeholder="placeholder"
leading-icon="i-lucide-search"
/>
</template>
6. Verify GREEN Phase
Run tests again to confirm they pass:
pnpm test:storybook:run -- {ComponentName}
Expected: Tests pass (GREEN phase).
7. Update test.json for GREEN Phase
Update test state to "passed":
{
"tests": [
{
"name": "Default",
"state": "passed",
"note": "GREEN phase - basic SearchBar with search icon"
}
]
}
8. Repeat for Next Test
Add the NEXT test ONE AT A TIME and repeat steps 3-7.
Common Second Tests:
- Loading state
- Error state
- Disabled state
- With icon/feature
- Required field
Example Second Test:
export const Loading: Story = {
args: {
placeholder: 'Searching...',
loading: true,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = await canvas.findByPlaceholderText(/searching/i);
await expect(input).toBeInTheDocument();
const container = input.parentElement;
const spinner = container?.querySelector('[class*="i-lucide"][class*="loader-circle"]');
await expect(spinner).toBeInTheDocument();
},
};
TDD Guard Enforcement
The TDD guard hook enforces strict discipline. Common violations:
Multiple Test Addition
Violation: Adding multiple tests/stories at once Fix: Add ONE test at a time
Premature Implementation
Violation: Creating component without RED phase evidence Fix: Update test.json with failure evidence before implementing
Over-Implementation
Violation: Adding untested props or functionality Fix: Only implement what's needed for the current failing test
Missing RED Phase
Violation: Implementing without test failure captured Fix: Run tests, save output, update test.json with exit code and errors
NuxtUI Component Patterns
Wrapping UInput (Atom Example)
<script setup lang="ts">
defineProps<{
placeholder?: string;
type?: 'text' | 'email' | 'password' | 'search';
disabled?: boolean;
}>();
</script>
<template>
<UInput
:placeholder="placeholder"
:type="type"
:disabled="disabled"
/>
</template>
Combining UFormField + UInput (Molecule Example)
<script setup lang="ts">
defineProps<{
label?: string;
name?: string;
placeholder?: string;
required?: boolean;
}>();
</script>
<template>
<UFormField :label="label" :name="name" :required="required">
<UInput :placeholder="placeholder" :name="name" :required="required" />
</UFormField>
</template>
Common NuxtUI Props
Support these props when tests require them:
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'color?: 'primary' | 'secondary' | 'success' | 'error'variant?: 'solid' | 'outline' | 'soft' | 'ghost'loading?: booleandisabled?: booleanrequired?: boolean
Storybook Testing Patterns
Finding Elements
// By placeholder
const input = await canvas.findByPlaceholderText(/search/i);
// By label
const label = await canvas.findByLabelText(/email/i);
// By text content
const error = await canvas.findByText(/error message/i);
// By role
const button = await canvas.findByRole('button', { name: /submit/i });
Common Assertions
// Element exists
await expect(input).toBeInTheDocument();
// Has attribute
await expect(input).toHaveAttribute('required');
await expect(input).toHaveAttribute('type', 'search');
// Icon/spinner presence (use attribute selectors)
const icon = container?.querySelector('[class*="i-lucide"][class*="search"]');
await expect(icon).toBeInTheDocument();
Test Coverage Guidelines
Minimum tests per component:
- Atoms: 2-3 tests (Default, Disabled, Loading/Error)
- Molecules: 3-5 tests (Default, With[Feature], State variations)
- Organisms: 5-10 tests (Multiple features, interactions, edge cases)
What to test:
- Basic rendering with default props
- Props affecting appearance/behavior
- State variations (loading, disabled, error, empty)
- Interactions (if applicable)
- Validation and error messages
- Accessibility (ARIA attributes, keyboard navigation)
Common Pitfalls
Pitfall 1: Storybook Hot Reload Issues
Problem: Tests fail with "failed to fetch" after creating component Solution: Kill and restart Storybook server, wait for full rebuild
Pitfall 2: Invalid querySelector Syntax
Problem: querySelector('.iconify.i-lucide\\:icon') fails
Solution: Use attribute selectors: [class*="i-lucide"][class*="icon"]
Pitfall 3: Vue Prop Forwarding
Problem: Props work without explicit declaration Solution: Leverage Vue's forwarding when appropriate, add explicit props when tests require them
Pitfall 4: TDD Guard Blocks Refactoring
Problem: Guard blocks even reasonable code improvements Solution: Only refactor tested code; add new tests before new features
Running Tests
# Run all Storybook tests
pnpm test:storybook:run
# Run specific component tests
pnpm test:storybook:run -- ComponentName
# Run in watch mode (development)
pnpm test:storybook
# Run with coverage
pnpm test:storybook:run --coverage
Success Criteria
A component is complete when:
- All planned tests pass (100% GREEN)
- Component follows naming conventions
- Props are properly typed with JSDoc comments
- Storybook autodocs enabled
- Atomic design level is correct
- Minimum test coverage achieved
Resources
References
- references/naming-conventions.md - Complete component naming and organizational guide following Atomic Design principles
- references/tdd-workflow.md - Detailed TDD workflow documentation with troubleshooting, patterns, and test.json management
Assets
- assets/story-template.ts - Storybook story file template with TDD comments and common patterns
- assets/component-template.vue - Vue component template with NuxtUI patterns and prop examples
Quick Start Checklist
For each new component:
- Determine atomic level (atom/molecule/organism)
- Create story file with ONE Default test
- Run test to confirm failure (RED)
- Save failure output to /tmp
- Update test.json with RED phase evidence
- Implement minimal component code
- Run test to confirm success (GREEN)
- Update test.json with GREEN phase
- Repeat for next test
Integration with Other Skills
This skill works well with:
- nuxt-ui - For NuxtUI component documentation and patterns
- tailwindcss - For styling and utility classes
- turborepo - For monorepo build and test execution
More from akornmeier/claude-config
docker
Guide for using Docker - a containerization platform for building, running, and deploying applications in isolated containers. Use when containerizing applications, creating Dockerfiles, working with Docker Compose, managing images/containers, configuring networking and storage, optimizing builds, deploying to production, or implementing CI/CD pipelines with Docker.
12motion-vue
Motion for Vue animation library guidance. Use when building Vue animations with motion-v package, implementing gesture animations (hover, press, drag), scroll-linked animations, layout animations, exit animations with AnimatePresence, variants, spring physics, or using hooks like useAnimate, useScroll, useSpring, useMotionValue, useTransform. Triggers on keywords like motion.div, motion-v, whileHover, whilePress, whileDrag, whileInView, AnimatePresence, layout animations, scroll animations, MotionConfig, LayoutGroup.
12cloudflare-r2
Guide for implementing Cloudflare R2 - S3-compatible object storage with zero egress fees. Use when implementing file storage, uploads/downloads, data migration to/from R2, configuring buckets, integrating with Workers, or working with R2 APIs and SDKs.
4cloudflare
Guide for building applications on Cloudflare's edge platform. Use when implementing serverless functions (Workers), edge databases (D1), storage (R2, KV), real-time apps (Durable Objects), AI features (Workers AI, AI Gateway), static sites (Pages), or any edge computing solutions.
4template-skill
Replace with description of the skill and when Claude should use it.
4