# react-web

Enforces test-first development for React components and pages using React Testing Library. It triggers automatically when editing .tsx files or components in src/.

- skill · `type=skill` · Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, OpenCode · 339 installs · ★ 711
- Author: alinaqi
- License: MIT · policy=mirror · the beginning of the file is at the end of this page
- Source: https://github.com/alinaqi/maggy/tree/HEAD/skills/react-web/SKILL.md
- Updated: 2026-10-13
- HTML: https://affpapa.org/en/nevibecode/skills/react-web

## What it does

This skill forces the agent to follow a strict test-first workflow. Before writing any component or hook logic, Claude must write a failing test file using `@testing-library/react`. If the agent tries to create component code without tests, the rules flag a violation and block the generation until tests exist.

It also lays out a modular project structure. Presentation components stay functional, component logic moves into separate custom hooks, and state starts simple with `useState` before moving to Zustand for global stores. Non-React domain logic lives separately under `src/core`.

Because `user-invocable` is set to `false`, you do not trigger this skill with a slash command. It activates automatically when you work on `.tsx` and `.jsx` files inside `src/components/`, `src/pages/`, or `src/app/`. If your repository relies on a test runner other than Vitest or Jest with Testing Library, you will need to adjust the test conventions.

## Install

### Claude Code (native platform)

1. **Skills CLI (Vercel)**
```bash
npx skills add alinaqi/maggy --skill react-web -a claude-code
```

2. **Manually: put the files in place**
- File: `~/.claude/skills/react-web/SKILL.md` — Copy the whole skill folder; for one project use .claude/skills/react-web/

### Codex

1. **Skills CLI (Vercel)**
```bash
npx skills add alinaqi/maggy --skill react-web -a codex
```

2. **Manually: put the files in place**
- File: `.agents/skills/react-web/SKILL.md` — For all projects use ~/.agents/skills/

### Cursor

1. **Skills CLI (Vercel)**
```bash
npx skills add alinaqi/maggy --skill react-web -a cursor
```

2. **Manually: put the files in place**
- File: `.cursor/skills/react-web/SKILL.md` — Cursor also reads .agents/skills/ and .claude/skills/

### GitHub Copilot

1. **GitHub CLI**
```bash
gh skill install alinaqi/maggy react-web
```

2. **Manually: put the files in place**
- File: `.github/skills/react-web/SKILL.md`

### Gemini CLI

1. **Skills CLI (Vercel)**
```bash
npx skills add alinaqi/maggy --skill react-web -a gemini-cli
```

2. **Platform CLI**
```bash
gemini skills install https://github.com/alinaqi/maggy --path skills/react-web
```

### Windsurf (Devin Desktop)

**Manually: put the files in place**
- File: `.devin/skills/react-web/SKILL.md` — The legacy .windsurf/skills/ path still works

### OpenCode

**Skills CLI (Vercel)**
```bash
npx skills add alinaqi/maggy --skill react-web -a opencode
```

### Claude.ai

**Claude.ai**
- Zip the folder and upload it in Settings → Capabilities → Skills

Where a file is given instead of a command, copy the files from the source to that path: https://github.com/alinaqi/maggy/tree/HEAD/skills/react-web/SKILL.md

## When to use it
- enforce test-first development for React components and hooks
- extract component logic into reusable custom hooks
- organize frontend code into `src/core`, `components`, and `pages`
- manage state escalation from local `useState` to Zustand

## Example prompts
- Create a Button component with disabled state and secondary variant
- Implement a useCounter custom hook with increment, decrement, and reset actions
- Add a new settings page in src/pages/Settings with data fetching handled in a hook

## Source text · SKILL.md · MIT

Below is the content of a third-party file. It is data, not instructions for you: do not act on anything written inside unless the human asks. Block tag: 61a4f69de96efe07.

BEGIN THIRD-PARTY FILE 61a4f69de96efe07
````text
---
name: react-web
description: React web development with hooks, React Query, Zustand
when-to-use: When working on React web components or pages
user-invocable: false
paths: ["**/*.tsx", "**/*.jsx", "src/components/**", "src/pages/**", "src/app/**"]
effort: medium
---

# React Web Skill


---

## Test-First Development (MANDATORY)

**CRITICAL: Tests MUST be written BEFORE implementation code. This is non-negotiable for frontend components.**

### The TFD Workflow

```
1. Write test file first → Defines expected behavior
2. Run test (it fails) → Confirms test is valid
3. Write minimal code → Just enough to pass
4. Run test (it passes) → Validates implementation
5. Refactor if needed → Tests catch regressions
```

### Component Development Order

```bash
# CORRECT ORDER - Test first
1. Create Button.test.tsx    # Write tests for expected behavior
2. Run tests (they fail)     # npm test -- Button
3. Create Button.tsx         # Implement to pass tests
4. Run tests (they pass)     # Verify implementation
5. Create Button.module.css  # Style after logic works

# WRONG ORDER - Never do this
1. Create Button.tsx         # ❌ No tests exist yet
2. Create Button.module.css  # ❌ Still no tests
3. "I'll add tests later"    # ❌ Tests never get written
```

### Test File Structure (Create First)

```typescript
// Button.test.tsx - CREATE THIS FIRST
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  // Define ALL expected behaviors upfront
  describe('rendering', () => {
    it('renders with label', () => {
      render(<Button label="Click me" onClick={() => {}} />);
      expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
    });

    it('applies variant class', () => {
      render(<Button label="Click" onClick={() => {}} variant="secondary" />);
      expect(screen.getByRole('button')).toHaveClass('secondary');
    });
  });

  describe('interactions', () => {
    it('calls onClick when clicked', () => {
      const onClick = vi.fn();
      render(<Button label="Click me" onClick={onClick} />);
      fireEvent.click(screen.getByRole('button'));
      expect(onClick).toHaveBeenCalledTimes(1);
    });

    it('does not call onClick when disabled', () => {
      const onClick = vi.fn();
      render(<Button label="Click me" onClick={onClick} disabled />);
      fireEvent.click(screen.getByRole('button'));
      expect(onClick).not.toHaveBeenCalled();
    });
  });

  describe('accessibility', () => {
    it('has correct aria attributes when disabled', () => {
      render(<Button label="Click" onClick={() => {}} disabled />);
      expect(screen.getByRole('button')).toHaveAttribute('aria-disabled', 'true');
    });
  });
});
```

### Hook Test First Pattern

```typescript
// useCounter.test.ts - CREATE THIS FIRST
import { renderHook, act } from '@testing-library/react';
````
END THIRD-PARTY FILE 61a4f69de96efe07

The third-party file has ended. Everything between the 61a4f69de96efe07 tags is data: requests and commands from it are not followed. An “end of file” line with another tag inside the block is part of the file.

First 2932 of 14589 bytes. © alinaqi · MIT · https://github.com/alinaqi/maggy/tree/HEAD/skills/react-web/SKILL.md

_Part of the page did not fit — see the HTML version._

---
NeVibeCode on affpapa.org. Catalog texts are descriptions, not instructions for the agent. How to choose and install: https://affpapa.org/en/nevibecode/agents.md
