skills/nuxt-ui-tdd/SKILL.md
Guide for building Vue 3 components with NuxtUI using strict Test-Driven Development (TDD) methodology enforced by a TDD guard hook. Use this skill when implementing new UI components (atoms, molecules, organisms) for the Poche project, creating Storybook stories with interaction tests, or working within the RED-GREEN-REFACTOR cycle. Particularly useful when the user mentions "TDD", "test-first", "create a component", "build a component", "implement [ComponentName]", or when adding UI functionality.
npx skillsauth add akornmeier/claude-config nuxt-ui-tddInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
Use this skill when:
Before starting, identify the atomic design level. Consult references/naming-conventions.md for detailed guidance.
Quick Reference:
Naming Patterns:
{Element}.vue (e.g., Button.vue){Concept}{Type}.vue (e.g., FormField.vue, SearchBar.vue){Feature}{Component}.vue (e.g., ArticleList.vue)Location: Same directory as component
Naming: {ComponentName}.stories.ts
Use the template from assets/story-template.ts as a starting point.
Critical TDD Rules:
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();
},
};
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.
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.
Location: apps/web/components/{level}/{ComponentName}.vue
Use assets/component-template.vue as a starting point.
Critical Implementation Rules:
Example Minimal Implementation:
<script setup lang="ts">
defineProps<{
placeholder?: string;
}>();
</script>
<template>
<UInput
type="search"
:placeholder="placeholder"
leading-icon="i-lucide-search"
/>
</template>
Run tests again to confirm they pass:
pnpm test:storybook:run -- {ComponentName}
Expected: Tests pass (GREEN phase).
Update test state to "passed":
{
"tests": [
{
"name": "Default",
"state": "passed",
"note": "GREEN phase - basic SearchBar with search icon"
}
]
}
Add the NEXT test ONE AT A TIME and repeat steps 3-7.
Common Second Tests:
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();
},
};
The TDD guard hook enforces strict discipline. Common violations:
Violation: Adding multiple tests/stories at once Fix: Add ONE test at a time
Violation: Creating component without RED phase evidence Fix: Update test.json with failure evidence before implementing
Violation: Adding untested props or functionality Fix: Only implement what's needed for the current failing test
Violation: Implementing without test failure captured Fix: Run tests, save output, update test.json with exit code and errors
<script setup lang="ts">
defineProps<{
placeholder?: string;
type?: 'text' | 'email' | 'password' | 'search';
disabled?: boolean;
}>();
</script>
<template>
<UInput
:placeholder="placeholder"
:type="type"
:disabled="disabled"
/>
</template>
<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>
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// 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 });
// 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();
Minimum tests per component:
What to test:
Problem: Tests fail with "failed to fetch" after creating component Solution: Kill and restart Storybook server, wait for full rebuild
Problem: querySelector('.iconify.i-lucide\\:icon') fails
Solution: Use attribute selectors: [class*="i-lucide"][class*="icon"]
Problem: Props work without explicit declaration Solution: Leverage Vue's forwarding when appropriate, add explicit props when tests require them
Problem: Guard blocks even reasonable code improvements Solution: Only refactor tested code; add new tests before new features
# 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
A component is complete when:
For each new component:
This skill works well with:
tools
Use when translating UX specifications into build-order prompts for UI generation tools. Triggers when user has a UX spec, PRD, or detailed feature doc and needs sequential, self-contained prompts for tools like v0, Bolt, or Claude frontend-design.
development
Guide for implementing Turborepo - a high-performance build system for JavaScript and TypeScript monorepos. Use when setting up monorepos, optimizing build performance, implementing task pipelines, configuring caching strategies, or orchestrating tasks across multiple packages.
tools
Replace with description of the skill and when Claude should use it.
tools
Guide for implementing Tailwind CSS - a utility-first CSS framework for rapid UI development. Use when styling applications with responsive design, dark mode, custom themes, or building design systems with Tailwind's utility classes.