frontend
Universal React/Next.js frontend expert. Handles shadcn/ui components, Tailwind CSS, Server/Client Components, anti-duplication checks. Auto-activates on keywords "frontend", "UI", "component", "page", "shadcn", "React", "Next.js" or paths app/**, components/**.
- Category: discovered
- Tags: github, community
- Version: 1.0.0
- Author: walid
- Downloads: 0
- Updated: 2025-11-22
Download the latest version (v1.0.0) from this page.
SKILL.md
---
name: frontend
description: Universal React/Next.js frontend expert. Handles shadcn/ui components, Tailwind CSS, Server/Client Components, anti-duplication checks. Auto-activates on keywords "frontend", "UI", "component", "page", "shadcn", "React", "Next.js" or paths app/**, components/**.
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
---
# Frontend Development Skill
> **Universal React/Next.js Frontend Expert**
>
> Inspiré de : Vercel DX Principles, shadcn/ui Philosophy, Tailwind Best Practices
---
## Scope & Activation
**Chargé par:** EXECUTOR agent
**Auto-activé si keywords:**
- `frontend`, `UI`, `composant`, `page`, `dashboard`
- `React`, `Next.js`, `shadcn`, `Tailwind`
- `button`, `dialog`, `form`, `layout`
- Paths: `app/**`, `components/**`, `*.tsx`, `*.css`
**Frameworks supportés:**
- Next.js 14+ (App Router)
- React 18+ (avec Server/Client Components)
- shadcn/ui (Radix + Tailwind)
- Tailwind CSS 3+
---
## Auto-Détection Base Frontend (OBLIGATOIRE Phase 0)
**AVANT toute action, vérifier si base frontend existe dans projet:**
### Workflow Détection (STRICT)
```
1. Check si projet est NOUVEAU (pas de components/)
→ SI nouveau: Clone depuis BUILDER/.stack/
2. Check si components.json existe
→ SI absent: Clone depuis BUILDER/.stack/
→ SI présent: Read components.json
3. Read components.json → Récupère aliases
→ Vérifier: aliases.ui = "@/components/ui"
→ Vérifier: aliases.components = "@/components"
4. Glob components/ui/*.tsx
→ Liste tous composants shadcn disponibles
→ Update mental map: "[X] composants shadcn disponibles"
5. SI components/ui/ vide ou absent
→ Clone depuis BUILDER/.stack/
```
**Path base frontend (relatif):**
```
BUILDER/.stack/
```
**Localisation base:**
- Chercher dossier BUILDER/ en remontant arborescence
- Ou utiliser variable env si configurée
- Path par défaut: `../../BUILDER/.stack/` (depuis projet)
**Contenu base (à cloner):**
- `components/ui/` - 57 composants shadcn
- `app/globals.css` + `app/themes.css`
- `lib/utils.ts` + `lib/compose-refs.ts`
- `components.json` (config shadcn)
- `tsconfig.json`, `next.config.ts`, `postcss.config.mjs`
- `package.json` (dependencies optimisées)
**Commande clone (EXACTE - copy-paste ready):**
```bash
# ÉTAPE 1: Trouver BUILDER/.stack/ (méthode fiable)
# Cherche dans home directory
BUILDER_DIR=$(find ~ -maxdepth 3 -type d -name "BUILDER" 2>/dev/null | head -1)
# Si pas trouvé dans home, cherche partout (plus lent)
if [ -z "$BUILDER_DIR" ]; then
BUILDER_DIR=$(find /home -maxdepth 4 -type d -name "BUILDER" 2>/dev/null | head -1)
fi
# Path .stack/
BUILDER_STACK="$BUILDER_DIR/.stack"
# ÉTAPE 2: Vérifier .stack/ existe
if [ ! -d "$BUILDER_STACK" ]; then
echo "❌ BUILDER/.stack/ non trouvé"
echo ""
echo "Clone BUILDER repo d'abord:"
echo " cd ~"
echo " git clone https://github.com/nera0875/BUILDER.git"
echo ""
echo "Puis relance la commande"
exit 1
fi
# ÉTAPE 3: Clone base frontend
echo "📦 Clone base frontend depuis $BUILDER_STACK"
# Copy tous les fichiers ET dossiers (y compris hidden)
cp -r "$BUILDER_STACK"/* . 2>/dev/null
cp -r "$BUILDER_STACK"/.[!.]* . 2>/dev/null
# ÉTAPE 4: Install dependencies
echo "📥 Installation dependencies..."
npm install
echo "✅ Base frontend clonée (57 composants shadcn ready)"
```
**Alternative si find échoue (fallback manuel):**
```bash
# Si commande automatique échoue, donner instructions user:
echo "❌ Détection automatique échouée"
echo ""
echo "Clone manuellement:"
echo ""
echo "1. Localise BUILDER repo:"
echo " cd ~/BUILDER # ou ton path"
echo ""
echo "2. Copy .stack/ vers projet:"
echo " cp -r ~/.../BUILDER/.stack/* /ton/projet/"
echo ""
echo "3. Install dependencies:"
echo " cd /ton/projet && npm install"
```
**Principe:** find limité à 3-4 depth max (performance). Si projet BUILDER bien placé (~/BUILDER ou ~/tools/BUILDER), détection auto fonctionne.
**Exemple détection:**
```json
// components.json trouvé
{
"aliases": {
"ui": "@/components/ui",
"components": "@/components"
}
}
```
→ **Conclusion:** Kit shadcn installé, vérifier components/ui/
**Impact anti-duplication:**
- ❌ Ne JAMAIS recréer `components/ui/button.tsx` si existe
- ❌ Ne JAMAIS run `npx shadcn init` si components.json existe
- ❌ Ne JAMAIS run `npx shadcn add button` si components/ui/button.tsx existe
- ✅ Importer depuis `@/components/ui/button`
- ✅ Créer composants custom dans `components/features/` uniquement
---
## ❌ ZONES INTERDITES (READ-ONLY - JAMAIS TOUCHER)
### components/ui/ - KIT SHADCN (STRICTEMENT INTERDIT)
**RÈGLES ABSOLUES:**
- ❌ Créer nouveau fichier dans `components/ui/`
- ❌ Modifier fichiers existants `components/ui/*.tsx`
- ❌ Supprimer composants shadcn
- ❌ Renommer fichiers `ui/`
- ❌ Ajouter props custom aux composants ui/
- ❌ Refactor code dans `ui/`
**SEULE ACTION AUTORISÉE:**
- ✅ **Importer uniquement** depuis `@/components/ui/[composant]`
```tsx
// ✅ AUTORISÉ
import { Button } from "@/components/ui/button";
import { Card, CardHeader, CardTitle } from "@/components/ui/card";
// ❌ INTERDIT
// Modifier components/ui/button.tsx
// Créer components/ui/custom-button.tsx
```
**Raison:** `components/ui/` = Lego blocks de base shadcn. **READ-ONLY STRICT.** Customs dans `features/`.
---
### app/globals.css - STYLES GLOBAUX (1 SEUL FICHIER)
**INTERDIT:**
- ❌ Créer nouveaux fichiers `.css` (styles.css, custom.css, etc)
- ❌ CSS modules (`.module.css`)
- ❌ Styled-components
- ❌ CSS-in-JS (emotion, etc)
- ❌ Modifier directives Tailwind existantes (`@tailwind base`, etc)
- ❌ Supprimer CSS variables shadcn (`:root`, `.dark`)
**AUTORISÉ UNIQUEMENT:**
- ✅ Ajouter `@layer utilities` dans `globals.css` existant (si absolument nécessaire)
```css
/* ✅ AUTORISÉ dans globals.css existant */
@layer utilities {
.custom-scrollbar::-webkit-scrollbar {
width: 8px;
}
}
/* ❌ INTERDIT - Pas de nouveau fichier CSS */
/* Pas de custom.css, styles.css, etc */
```
**Raison:** 1 seul point styling global (Tailwind). Fragmentation = chaos maintenance.
---
### app/themes.css - THÈMES SHADCN (READ-ONLY)
**INTERDIT:**
- ❌ Modifier variables CSS existantes
- ❌ Supprimer thèmes
- ❌ Ajouter nouveaux thèmes (sauf demande explicite user)
**Raison:** Thèmes shadcn pré-configurés. Modification = casse dark mode.
---
### lib/utils.ts - CN HELPER (READ-ONLY)
**INTERDIT:**
- ❌ Modifier fonction `cn()` (helper shadcn)
- ❌ Supprimer imports `clsx` ou `tailwind-merge`
**AUTORISÉ:**
- ✅ Créer nouveaux helpers dans fichiers séparés: `lib/[custom].ts`
```tsx
// ❌ INTERDIT - Modifier lib/utils.ts
export function cn(...) { /* NE PAS TOUCHER */ }
// ✅ AUTORISÉ - Nouveau fichier
// lib/date-helpers.ts
export function formatDate(date: Date) {
return date.toLocaleDateString();
}
```
**Raison:** `cn()` = core shadcn helper. Modification = casse tous composants.
---
### Fichiers Config (READ-ONLY sauf cas spécifiques)
**INTERDIT de modifier sans raison:**
- ❌ `components.json` (config shadcn)
- ❌ `tsconfig.json` (TypeScript paths)
- ❌ `next.config.ts` (Next.js config)
- ❌ `postcss.config.mjs` (PostCSS config)
- ❌ `tailwind.config.ts` (si existe - Tailwind v3)
**AUTORISÉ seulement si:**
- ✅ Feature nécessite nouvelle lib → Update `next.config.ts`
- ✅ Nouveau alias path → Update `tsconfig.json` paths
- ✅ Nouveau plugin Tailwind → Update `tailwind.config.ts`
**Principe:** Config établie = stable. Modification = risque régression.
---
## ✅ OÙ CRÉER NOUVEAUX COMPOSANTS/FICHIERS
### components/features/[feature]/ - COMPOSANTS CUSTOM
**Structure recommandée:**
```
components/
├── ui/ ← READ-ONLY (shadcn)
│ └── button.tsx
├── layout/ ← CRÉER ICI (layouts app)
│ ├── sidebar.tsx
│ ├── header.tsx
│ └── footer.tsx
└── features/ ← CRÉER ICI (composants métier)
├── dashboard/
│ ├── stats-card.tsx ← Custom composant (utilise ui/card)
│ ├── chart.tsx
│ └── filters.tsx
├── kanban/
│ ├── board.tsx
│ ├── column.tsx
│ └── task-card.tsx ← Custom composant (utilise ui/card)
└── pomodoro/
└── timer.tsx
```
**Exemple composant custom:**
```tsx
// components/features/dashboard/stats-card.tsx
"use client";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { TrendingUp } from "lucide-react";
export function StatsCard({ title, value, trend }) {
return (
<Card className="border-2 border-primary/20 hover:shadow-lg transition">
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle className="text-sm font-medium">{title}</CardTitle>
<TrendingUp className="h-4 w-4 text-primary" />
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">{value}</p>
<p className="text-xs text-muted-foreground">
{trend > 0 ? "+" : ""}{trend}% from last month
</p>
</CardContent>
</Card>
);
}
```
**Principe:** Compose avec `ui/`, custom styling via Tailwind className, logique métier séparée.
---
### components/layout/ - LAYOUTS APP
**Créer ici:**
- Sidebar navigation
- Header/Navbar
- Footer
- AppShell/DashboardLayout
**Exemple:**
```tsx
// components/layout/sidebar.tsx
"use client";
import { Button } from "@/components/ui/button";
import { Home, Settings, User } from "lucide-react";
export function Sidebar() {
return (
<aside className="h-full border-r bg-card">
<nav className="flex flex-col gap-2 p-4">
<Button variant="ghost" className="justify-start">
<Home className="mr-2 h-4 w-4" />
Dashboard
</Button>
<Button variant="ghost" className="justify-start">
<User className="mr-2 h-4 w-4" />
Profile
</Button>
<Button variant="ghost" className="justify-start">
<Settings className="mr-2 h-4 w-4" />
Settings
</Button>
</nav>
</aside>
);
}
```
---
### app/[route]/ - PAGES NEXT.JS
**Structure app router:**
```
app/
├── globals.css ← READ-ONLY (sauf @layer utilities)
├── themes.css ← READ-ONLY
├── layout.tsx ← CRÉER (root layout)
├── page.tsx ← CRÉER (home page)
├── dashboard/
│ ├── layout.tsx ← CRÉER (dashboard layout avec sidebar)
│ ├── page.tsx ← CRÉER (dashboard home)
│ └── settings/
│ └── page.tsx ← CRÉER (nested route)
└── (auth)/ ← CRÉER (route group)
├── login/
│ └── page.tsx
└── register/
└── page.tsx
```
**Exemple page:**
```tsx
// app/dashboard/page.tsx (Server Component)
import { prisma } from "@/lib/prisma";
import { StatsCard } from "@/components/features/dashboard/stats-card";
export default async function DashboardPage() {
const stats = await prisma.task.groupBy({
by: ['status'],
_count: true
});
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 p-6">
{stats.map(stat => (
<StatsCard
key={stat.status}
title={stat.status}
value={stat._count}
trend={12}
/>
))}
</div>
);
}
```
---
### lib/ - UTILITIES CUSTOM
**Créer nouveaux helpers:**
```
lib/
├── utils.ts ← READ-ONLY (cn helper shadcn)
├── date-helpers.ts ← CRÉER (custom date utils)
├── api-client.ts ← CRÉER (fetch wrapper)
├── validators.ts ← CRÉER (Zod schemas)
└── constants.ts ← CRÉER (app constants)
```
**Exemple:**
```tsx
// lib/date-helpers.ts
import { format } from "date-fns";
export function formatDate(date: Date): string {
return format(date, "MMM dd, yyyy");
}
export function formatDateTime(date: Date): string {
return format(date, "MMM dd, yyyy HH:mm");
}
```
---
### hooks/ - CUSTOM REACT HOOKS
**Créer hooks custom:**
```
hooks/
├── use-local-storage.ts
├── use-media-query.ts
└── use-debounce.ts
```
**Exemple:**
```tsx
// hooks/use-local-storage.ts
"use client";
import { useState, useEffect } from "react";
export function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(initialValue);
useEffect(() => {
const item = window.localStorage.getItem(key);
if (item) setStoredValue(JSON.parse(item));
}, [key]);
const setValue = (value: T) => {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
};
return [storedValue, setValue] as const;
}
```
---
## Workflow Création Composant (STRICT - Phase par Phase)
### Phase 0: Vérification Base Clonée
**AVANT toute création composant:**
```bash
# Vérifier base frontend clonée depuis BUILDER/.stack/
ls components/ui/ && echo "✅ Base OK" || echo "❌ CLONE BASE FIRST"
```
**SI base absente:** Clone BUILDER/.stack/ (voir section Auto-Détection)
---
### Phase 1: Anti-Duplication Check (OBLIGATOIRE)
**⚡ OPTIMIZED: Grep RAG-Style (1-2 tool uses, 2s)**
**1. ✅ Check si composant shadcn existe dans `components/ui/`**
```bash
Grep "export.*Button" components/ui/ --output files_with_matches
→ components/ui/button.tsx (existe) → Réutilise
Grep "export.*Card" components/ui/ --output files_with_matches
→ components/ui/card.tsx (existe) → Réutilise
```
**Rappel:** BUILDER/.stack/ contient **57 composants shadcn**.
Ne JAMAIS recréer si existe.
**2. ✅ Check si composant custom existe dans `components/features/`**
```bash
Grep "export (default )?(function |const )?StatsCard" components/features/ -i --output files_with_matches
→ Si trouvé: components/features/dashboard/stats-card.tsx → Réutilise
→ Si vide: Composant existe pas → Créer
Grep "export.*TaskCard" components/features/ -i --output files_with_matches
→ Check existence (1 tool use, 2s)
```
**❌ PAS ÇA (lent - 30+ tools):**
```bash
ls components/ui/*.tsx → Liste 57 fichiers
Read button.tsx, Read card.tsx, Read dialog.tsx...
→ 30+ tool uses, 25s
```
**✅ ÇA (rapide - 1-2 tools):**
```bash
Grep "export.*[ComponentName]" components/ --output files_with_matches
→ 1 tool use, 2s
```
---
### Phase 2: Décision Emplacement
**3. ✅ Décide emplacement selon type:**
| Type composant | Emplacement | Exemple |
|----------------|-------------|---------|
| Layout app | `components/layout/` | `sidebar.tsx`, `header.tsx` |
| Composant métier | `components/features/[feature]/` | `stats-card.tsx`, `task-card.tsx` |
| Page Next.js | `app/[route]/page.tsx` | `app/dashboard/page.tsx` |
| Hook custom | `hooks/` | `use-local-storage.ts` |
| Util custom | `lib/` | `date-helpers.ts` |
---
### Phase 3: Composition avec Base
**4. ✅ Compose avec shadcn ui/ (base)**
- Import depuis `@/components/ui/[composant]`
- Styling via Tailwind className uniquement
- Logique métier custom dans composant
```tsx
// ✅ CORRECT - Composition
import { Card, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
export function StatsCard({ title, value }) {
return (
<Card className="border-2 border-primary/20"> {/* Custom styling */}
<CardHeader>
<CardTitle>{title}</CardTitle>
<p className="text-3xl font-bold">{value}</p>
</CardHeader>
</Card>
);
}
```
---
### Phase 4: Server vs Client
**5. ✅ "use client" si nécessaire (CHECKLIST)**
**OBLIGATOIRE "use client" SI:**
- ✅ Hooks React (`useState`, `useEffect`, `useContext`)
- ✅ Event handlers (`onClick`, `onChange`, `onSubmit`)
- ✅ Browser APIs (`window`, `document`, `localStorage`)
- ✅ Third-party libs interactives (react-hook-form)
**PAS "use client" SI:**
- ✅ Fetch data serveur (Prisma, fetch API)
- ✅ Pas d'interactivité (display statique)
- ✅ SEO important
```tsx
// ✅ Client Component (hooks + events)
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
export function Counter() {
const [count, setCount] = useState(0);
return <Button onClick={() => setCount(count + 1)}>{count}</Button>;
}
```
```tsx
// ✅ Server Component (fetch data)
import { prisma } from "@/lib/prisma";
export default async function TasksPage() {
const tasks = await prisma.task.findMany();
return <div>{tasks.map(t => <div key={t.id}>{t.name}</div>)}</div>;
}
```
---
### Pattern création composant custom
```tsx
// components/features/kanban/task-card.tsx
"use client";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Trash, Edit } from "lucide-react";
interface TaskCardProps {
task: {
id: string;
title: string;
priority: "low" | "medium" | "high";
};
onEdit: (id: string) => void;
onDelete: (id: string) => void;
}
export function TaskCard({ task, onEdit, onDelete }: TaskCardProps) {
return (
<Card className="group hover:shadow-md transition cursor-pointer">
<CardHeader className="flex flex-row items-start justify-between">
<div className="flex-1">
<CardTitle className="text-base">{task.title}</CardTitle>
<Badge
variant={task.priority === "high" ? "destructive" : "secondary"}
className="mt-2"
>
{task.priority}
</Badge>
</div>
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition">
<Button
variant="ghost"
size="icon"
onClick={() => onEdit(task.id)}
>
<Edit className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => onDelete(task.id)}
>
<Trash className="h-4 w-4 text-destructive" />
</Button>
</div>
</CardHeader>
</Card>
);
}
```
**Checklist:**
- ✅ Imports depuis `@/components/ui/`
- ✅ "use client" (car onClick events)
- ✅ TypeScript interface
- ✅ Styling Tailwind uniquement
- ✅ Composition (Card + Button + Badge depuis ui/)
- ✅ Props typées
- ✅ Logique métier (onEdit, onDelete callbacks)
---
## Architecture Stricte (Non-Negotiable)
### tsconfig.json paths (OBLIGATOIRE)
```json
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
```
**OU** si pas de `/src`:
```json
{
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}
```
---
### Structure Projet Standard (après clone BUILDER/.stack/)
**Structure EXACTE après clone base:**
```
projet/
├── .gitignore ← DE BASE
├── README.md ← PEUT modifier (doc projet)
├── package.json ← DE BASE (modifiable npm install)
├── tsconfig.json ← DE BASE (READ-ONLY sauf alias)
├── next.config.ts ← DE BASE (READ-ONLY sauf feature)
├── postcss.config.mjs ← DE BASE (READ-ONLY)
├── components.json ← DE BASE (READ-ONLY)
│
├── app/
│ ├── globals.css ← DE BASE (READ-ONLY sauf @layer)
│ ├── themes.css ← DE BASE (READ-ONLY)
│ ├── layout.tsx ← CRÉER (root layout)
│ ├── page.tsx ← CRÉER (home page)
│ ├── dashboard/ ← CRÉER (feature routes)
│ │ ├── layout.tsx
│ │ └── page.tsx
│ └── (auth)/ ← CRÉER (route groups)
│ ├── login/
│ └── register/
│
├── components/
│ ├── ui/ ← DE BASE (57 composants READ-ONLY)
│ │ ├── accordion.tsx
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── dialog.tsx
│ │ └── ... (53 autres)
│ ├── layout/ ← CRÉER (layouts app)
│ │ ├── sidebar.tsx
│ │ └── header.tsx
│ └── features/ ← CRÉER (composants métier)
│ ├── dashboard/
│ │ ├── stats-card.tsx
│ │ └── chart.tsx
│ └── kanban/
│ └── board.tsx
│
├── lib/
│ ├── utils.ts ← DE BASE (READ-ONLY)
│ ├── compose-refs.ts ← DE BASE (READ-ONLY)
│ ├── prisma.ts