- description
- React patterns with destructured props, compiler optimization, Effects, and Tailwind v4 syntax. ALWAYS use when using React.
- name
- react
- metadata
- {"skiller":{"source":".agents/rules/react.mdc"}}
## Requirements
- Always use destructured props in function parameters
- Define TypeScript types inline with the destructured props
- Avoid creating separate interfaces for component props
- Avoid non-destructured props that require additional destructuring inside the component
- Use available UI components from the component library
- Use CSS variables from globals.css for consistent theming
- Use Tailwind v4 syntax (see [Tailwind v4 Syntax](#tailwind-v4-syntax))
- DO NOT add icon margin in Button, DropdownMenuItem: use gap-2 instead
- Use `useEffectEvent` to extract non-reactive logic from Effects (see [Separating Events from Effects](#separating-events-from-effects))
- NEVER suppress the dependency linter with `eslint-disable` - use `useEffectEvent` instead
- Use custom breakpoint syntax for responsive design (see [App-Specific Rules](#app-specific-rules))
## Tailwind v4 Syntax
- **Inline opacity**: Use `/` for opacity (`bg-black/50 text-white/80`)
- v3: `bg-black bg-opacity-50 text-white text-opacity-80`
- Removed: `bg-opacity-*`, `text-opacity-*`, `border-opacity-*`
- **Renamed utilities**: `shadow-sm` → `shadow-xs`, `rounded-sm` → `rounded-xs`, `blur-sm` → `blur-xs`
- **CSS variables**: Use parentheses `bg-(--brand-color)` instead of brackets `bg-[--brand-color]`
- **Important modifier**: Exclamation mark at the end (`size-5!`) instead of beginning (`!size-5`)
- **Descendant selectors**: Use `**:` for all descendants (replaces `[&_*]:`) and `*:` for direct children (replaces `[&>*]:`)
- All descendants: `**:px-4` instead of `[&_*]:px-4`
- Direct children: `*:px-4` instead of `[&>*]:px-4`
- Element filtering: `*:[a]:underline` instead of `[&>a]:underline`, `**:[a]:underline` instead of `[&_a]:underline`
- Data attributes: `*:data-[slot=field-label]:flex-auto` instead of `*:[[data-slot=field-label]]:flex-auto`. Keep `[[data-...]]` if there are other selectors.
- Context-aware (in-): `in-data-[slot=tooltip-content]:text-background` instead of `[[data-slot=tooltip-content]_&]:text-background`
- **Composable variants**: Chain variants together (`group-has-data-selected:opacity-100`, `data-highlighted:ring-2`)
- **Canonical classes**: Always prefer built-in utilities over arbitrary values (e.g., `w-full` instead of `w-[100%]`, `translate-x-full` instead of `translate-x-[100%]`)
- **Default changes**: Border color now `currentColor` (was `gray-200`), ring width now 1px (was 3px)
# Component Patterns
## Design Tokens
Design tokens are semantic CSS variables that separate theme, context, and usage. Rather than hardcoding colors, use a semantic naming convention that creates layers of abstraction.
### Variable Architecture
```css
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
}
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
}
```
Common tokens:
- `--background` - Page background color
- `--foreground` - General text color
- `--primary` - Main brand color
- `--primary-foreground` - Text color against primary
This creates a maintainable, flexible system that scales across applications.
## Data Attribute Styling Patterns
Shadcn/ui and Radix UI components use data attributes (`data-state`, `data-slot`) to enable flexible styling without prop explosion. Use these patterns when working with components that expose data attributes.
### Styling with data-state
Components expose their state through `data-state` attributes. Use Tailwind's arbitrary variant syntax to style based on component state:
```tsx
<Dialog
className={cn(
// Base styles
"rounded-lg border p-4",
// State-based styles
"data-[state=open]:animate-in data-[state=open]:fade-in",
"data-[state=closed]:animate-out data-[state=closed]:fade-out",
// Multiple attributes
"data-[state=open][data-side=top]:slide-in-from-top-2",
)}
/>
```
For commonly-used states, extend Tailwind's configuration:
```js title="tailwind.config.js"
module.exports = {
theme: {
extend: {
data: {
open: 'state="open"',
closed: 'state="closed"',
active: 'state="active"',
},
},
},
};
```
Then use shorthand:
```tsx
<Dialog className="data-open:opacity-100 data-closed:opacity-0" />
```
### Radix UI Data Attributes
Radix UI automatically applies data attributes to its primitives:
```tsx
import * as Dialog from "@radix-ui/react-dialog";
<Dialog.Root>
<Dialog.Trigger />
<Dialog.Portal>
{/* Radix automatically adds data-state="open" | "closed" */}
<Dialog.Overlay className="data-[state=open]:animate-in data-[state=closed]:animate-out" />
<Dialog.Content className="data-[state=open]:fade-in data-[state=closed]:fade-out" />
</Dialog.Portal>
</Dialog.Root>;
```
Common Radix data attributes:
- `data-state` - open/closed, active/inactive, on/off
- `data-side` - top/right/bottom/left (for positioned elements)
- `data-align` - start/center/end (for positioned elements)
- `data-orientation` - horizontal/vertical
- `data-disabled` - present when disabled
- `data-placeholder` - present when showing placeholder
### Common State Patterns
Use data attributes for all kinds of component state:
```tsx
// Open/closed state
<Accordion data-state={isOpen ? 'open' : 'closed'} />
// Selected state
<Tab data-state={isSelected ? 'active' : 'inactive'} />
// Disabled state (in addition to disabled attribute)
<Button data-disabled={isDisabled} disabled={isDisabled} />
// Loading state
<Button data-loading={isLoading} />
// Orientation
<Slider data-orientation="horizontal" />
// Side/position
<Tooltip data-side="top" />
```
### Using data-slot for Component Targeting
Components use `data-slot` attributes for stable identifiers that can be targeted by parents.
**Using `has-[]` for parent-aware styling:**
```tsx
<form
data-slot="form"
className={cn(
"space-y-4",
// Adjust spacing when specific slots are present
"has-[>[data-slot=form-section]]:space-y-6",
"has-[>[data-slot=inline-fields]]:space-y-2",
// Style based on slot states
"has-[[data-slot=submit-button][data-loading=true]]:opacity-50",
)}
>
{children}
</form>
```
**Using `[&_]` for descendant targeting:**
```tsx
<div
data-slot="card"
className={cn(
"rounded-lg border p-4",
// Target any descendant with data-slot
"[&_[data-slot=card-header]]:mb-4",
"[&_[data-slot=card-title]]:text-lg [&_[data-slot=card-title]]:font-semibold",
"[&_[data-slot=card-description]]:text-muted-foreground [&_[data-slot=card-description]]:text-sm",
"[&_[data-slot=card-footer]]:mt-4 [&_[data-slot=card-footer]]:border-t [&_[data-slot=card-footer]]:pt-4",
)}
>
{children}
</div>
```
### Global CSS with data-slot
Use global CSS for theme-wide component styling:
```css title="globals.css"
/* Style all buttons within forms */
[data-slot="form"] [data-slot="button"] {
@apply w-full @xl:w-auto;
}
/* Style submit buttons specifically */
[data-slot="form"] [data-slot="submit-button"] {
@apply bg-primary text-primary-foreground;
}
/* Adjust inputs within inline layouts */
[data-slot="inline-fields"] [data-slot="input"] {
@apply flex-1;
}
/* Style based on state combinations */
[data-slot="dialog"][data-state="open"] [data-slot="dialog-content"] {
@apply animate-in fade-in;
}
```
### data-slot Naming Conventions
Follow these conventions for consistent `data-slot` naming:
1. **Use kebab-case** - `data-slot="form-field"` not `data-slot="formField"`
2. **Be specific** - `data-slot="submit-button"` not `data-slot="button"`
3. **Match component purpose** - Name reflects what it does, not how it looks
4. **Avoid implementation details** - `data-slot="user-avatar"` not `data-slot="rounded-image"`
```tsx
// ✅ Good examples
data-slot="search-input"
data-slot="navigation-menu"
data-slot="error-message"
data-slot="submit-button"
data-slot="card-header"
// ❌ Avoid
data-slot="input" // Too generic
data-slot="blueButton" // Includes styling
data-slot="div-wrapper" // Implementation detail
data-slot="mainContent" // Use kebab-case
```
### When to Use Data Attributes vs Props
**Use `data-state` for:**
- Visual states (open/closed, active/inactive, loading)
- Layout states (orientation, side, alignment)
- Interaction states (hover, focus, disabled when styling children)
**Use `data-slot` for:**
- Component identification with stable identifiers
- Parent-child composition patterns
- Theme-wide component styling
- Variant-independent targeting
**Use props for:**
- Variants (primary, secondary, destructive)
- Sizes (sm, md, lg)
- Behavioral configuration (controlled/uncontrolled, defaults)
- Event handlers (onClick, onChange)
**Combined example:**
```tsx
const Button = ({ variant = 'primary', size = 'md', loading, disabled, className, ...props }) => (
<button
data-slot="button"
data-loading={loading}
data-disabled={disabled}
className={cn(buttonVariants({ variant, size }), className)}
disabled={disabled}
{...props}
/>
);
// Usage
<Button variant="primary" size="lg">Submit</Button>
<form className="[&_[data-slot=button]]:w-full"><Button>Submit</Button></form>
<Button loading={isLoading} className="data-[loading=true]:opacity-50">Submit</Button>
```
# React Compiler
## Context
This project uses React Compiler, which automatically optimizes your React code through automatic memoization at build time. Manual memoization with `useMemo`, `useCallback`, and `React.memo` is rarely needed and often introduces unnecessary complexity.
## Core Principle
**Write clean, idiomatic React code. Let the compiler optimize it.**
React Compiler automatically applies optimal memoization based on data flow analysis. It can even optimize cases that manual memoization cannot handle, such as memoizing values after conditional returns or within complex control flow.
## Requirements
### DO NOT Use Manual Memoization
- **NEVER** wrap components with `React.memo` unless you have a specific, documented reason
- **NEVER** use `useMemo` for performance optimization - the compiler handles this
- **NEVER** use `useCallback` for performance optimization - the compiler handles this
Voir sur GitHub