Skip to main content

AI SKILL

Prompt

If you want to enhance your AI agent with this pattern, you can use the following prompt as a rule prompt or skill. Use the copy button (top right) to copy the prompt and paste it into your AI agent.

---
name: react-file-nesting-pattern
description: The canonical way to organize React components in this repository as a nested file tree using the `@ComponentName/` convention. Load this skill whenever creating a new component, splitting a large or monolithic component, deciding where child components / hooks / styles should live, or reviewing component file structure. Use it any time you write JSX composed of multiple sections, extract a sub-component, add colocated support files (CSS, hooks, types), or wonder "where should this file go?" — even if the user doesn't name the pattern explicitly.
---

# React File Nesting Pattern

## Overview

When creating and composing React components in this project, use the React File Nesting Pattern. It follows React's core mental model: **understand your UI as a tree of components**, and make the file system mirror that tree.

The project uses the [File Nesting Explorer](https://explorer.groverlee.me/) editor extension to render these `@`-prefixed folders as a collapsible tree under their parent component, so a component and everything it owns reads as one unit in the file explorer.

The goal is maintainability and clarity. Before splitting, always ask: **"Would splitting this component make the codebase easier to understand and maintain?"** If yes, split. If it only adds ceremony, keep it together.

## Core Principles

1. **No monolithic components** — never pile all logic, markup, and state into one file.
2. **Tree structure** — organize components hierarchically so the folders mirror the UI tree.
3. **Separation of concerns** — each component has a single, focused responsibility.
4. **Self-contained logic** — a component owns its own logic and does not reach into siblings.
5. **Colocate context** — read context in the component that actually needs it, not the parent.

## File Structure Convention

A component that has children gets a sibling folder prefixed with `@`, named exactly after the component:

```
ComponentName.tsx
@ComponentName/
├── ChildComponent.tsx
├── AnotherChild.tsx
└── @AnotherChild/
└── GrandchildComponent.tsx
```

The `@` prefix is what the File Nesting Explorer keys on to nest the folder under its parent. **The folder name after `@` must match the component filename exactly, including case**: `ActionBar.tsx` pairs with `@ActionBar/`, never `@action-bar/`. The pairing is derived from the filename minus its extension — a case or spelling mismatch breaks the link and the folder won't nest under its component. Children import nothing from parents; parents import children by relative path.

Note the distinction between the two kinds of folders in this pattern:

- **`@ComponentName/`** (PascalCase, `@`-prefixed) — holds a component's _children_. Named after the component file it belongs to.
- **`component-name/`** (kebab-case, no prefix) — a _component folder_ wrapping one component and its support files (see "Support files" below). Named freely in kebab-case because it isn't paired to a filename.

## Splitting a component: the canonical example

### ❌ Avoid — one file holding everything

```tsx
export const MyComponent = () => {
const myContext1 = useContext(MyContext1);
const myContext2 = useContext(MyContext2);

const handleCreate = () => {
// uses myContext1
};
const handleDelete = () => {
// uses myContext2
};

return (
<div>
<div>
<h1>My Component</h1>
<div>....</div>
</div>
<div>
<p>My Component Content</p>
{/* uses myContext2 */}
</div>
<div>
<button onClick={handleCreate}>Create</button>
<button onClick={handleDelete}>Delete</button>
</div>
</div>
);
};
```

Every context and handler lives at the top even though each is used by exactly one section. The file grows without bound and merge conflicts concentrate here.

### ✅ Correct — nested tree, logic pushed to where it's used

File structure:

```
MyComponent.tsx
@MyComponent/
├── Header.tsx
├── Content.tsx
├── ActionBar.tsx
└── @ActionBar/
├── CreateButton.tsx
└── DeleteButton.tsx
```

`MyComponent.tsx` — a thin composition root that just describes the tree:

```tsx
import { Header } from "./@MyComponent/Header";
import { Content } from "./@MyComponent/Content";
import { ActionBar } from "./@MyComponent/ActionBar";

export const MyComponent = () => (
<div>
<Header />
<Content />
<ActionBar />
</div>
);
```

`@MyComponent/Header.tsx` — purely presentational:

```tsx
export const Header = () => (
<div>
<h1>My Component</h1>
<div>....</div>
</div>
);
```

`@MyComponent/Content.tsx` — reads only the context it needs:

```tsx
export const Content = () => {
const myContext2 = useContext(MyContext2);
return (
<div>
<p>My Component Content</p>
{/* uses myContext2 */}
</div>
);
};
```

`@MyComponent/ActionBar.tsx` — composes its own children:

```tsx
import { CreateButton } from "./@ActionBar/CreateButton";
import { DeleteButton } from "./@ActionBar/DeleteButton";

export const ActionBar = () => (
<div>
<p>Action Content</p>
<CreateButton />
<DeleteButton />
</div>
);
```

`@MyComponent/@ActionBar/CreateButton.tsx` and `DeleteButton.tsx` — each owns its handler and its context:

```tsx
export const CreateButton = () => {
const myContext1 = useContext(MyContext1);
const handleCreate = () => {
// uses myContext1
};
return <button onClick={handleCreate}>Create</button>;
};
```

```tsx
export const DeleteButton = () => {
const myContext2 = useContext(MyContext2);
const handleDelete = () => {
// uses myContext2
};
return <button onClick={handleDelete}>Delete</button>;
};
```

Notice the win: `MyComponent` no longer touches either context, and prop drilling disappears because each leaf reads what it needs directly. This is why colocating context beats hoisting it — it keeps the parent ignorant of details it doesn't care about.

## When to split vs. keep together

These are heuristics, not hard cutoffs. For the hard ceiling, **apply the component size limit the project already defines** (check the project's contribution guidelines, coding conventions, or lint rules for a max-lines setting); **if the project defines none, take ~150 lines as the limit**. Either way, this pattern is how you stay well clear of the ceiling — start splitting long before you approach it.

**Split when:**

- The component has multiple distinct UI sections (header / content / footer).
- Logic is getting complex and a piece of it can be isolated.
- State or context can be localized to a child instead of held at the top.
- The file is growing toward the project's size limit — don't wait to hit it.
- Several event handlers serve clearly different purposes.
- A child could plausibly be reused elsewhere.

**Keep together when:**

- The component is genuinely atomic (a button, input, simple element).
- Splitting would add indirection without buying clarity.
- It's small (say, under ~50 lines) with one clear purpose.

## Support files → colocate in component folders

When a component owns support files — styles, hooks, utilities, types, tests — promote it from a standalone file to a **folder** and put everything it owns beside it. This keeps a component fully portable: you can move or delete it as a unit.

### ❌ Avoid — centralized support files at the parent level

```
styles.css // all styles for all children mixed together
MyComponent.tsx
@MyComponent/
├── Header.tsx
├── Content.tsx
└── ActionBar.tsx
```

It becomes hard to tell which styles belong to which child, and children can't be moved without hunting through shared files.

### ✅ Correct — each component folder owns its support files

```
MyComponent.css // styles for MyComponent itself only
MyComponent.tsx
@MyComponent/
├── header/
│ ├── Header.tsx
│ ├── Header.css
│ └── useHeaderState.ts
├── Content.tsx
└── ActionBar.tsx
```

Deeper example — a nested component with its own support files:

```
@MyComponent/
└── action-bar/
├── ActionBar.tsx
├── ActionBar.css
└── @ActionBar/
└── create-button/
├── CreateButton.tsx
├── CreateButton.css
└── useCreateAction.ts
```

Read this example carefully — it shows both folder kinds working together:

- `action-bar/` and `create-button/` are kebab-case **component folders**: wrappers grouping a component with its support files.
- `@ActionBar/` is the PascalCase **nesting folder** for `ActionBar.tsx`'s children. It sits _next to_ `ActionBar.tsx` inside its component folder and takes its name from that file — not from the surrounding `action-bar/` folder. Writing `@action-bar/` here would be wrong: the explorer pairs `@X/` with `X.tsx`, and there is no `action-bar.tsx`.

Usage of colocated support files:

```tsx
// @MyComponent/header/Header.tsx
import "./Header.css";
import { useHeaderState } from "./useHeaderState";

export const Header = () => {
const state = useHeaderState();
return <div className="header">...</div>;
};
```

### Create a folder when… / keep a single file when…

Create a folder when the component has any of: dedicated styles, one or more custom hooks, its own type definitions, helpers used only by it, test files, or local constants/config.

Keep it a single file when it has no support files, is purely presentational, or only imports from shared/common directories.

## Naming conventions

1. **Nesting folders (`@`-prefixed)**: PascalCase, matching the component filename exactly — `@Header/`, `@ActionBar/`. The name is not a style choice: the File Nesting Explorer pairs `@X/` with `X.tsx` by exact name, so `@action-bar/` next to `ActionBar.tsx` silently breaks the nesting.
2. **Component folders (support-file wrappers)**: lowercase kebab-case — `header/`, `action-bar/`. These aren't paired to any file, so kebab-case keeps them visually distinct from component files and `@`-folders.
3. **Component files**: PascalCase — `Header.tsx`, `ActionBar.tsx`.
4. **Style files**: PascalCase, matching the component they style — `Header.css`, `ActionBar.css`, `CreateButton.css`. A stylesheet is a support file _of a specific component_, not of the folder, so it takes the component's name. This makes the pairing unambiguous (one glance tells you `ActionBar.css` styles `ActionBar.tsx`), keeps the two files adjacent in any alphabetical listing, makes a component rename a mechanical same-name rename, and matches the ecosystem convention for component-scoped styles (`ActionBar.module.css` in CSS Modules). A kebab-case `action-bar.css` names the file after the folder instead — indirection that breaks as soon as the folder holds anything else.
5. **Hook files**: camelCase with `use` prefix — `useHeaderData.ts`, `useCreateAction.ts`.
6. **Type/constant files**: lowercase — `types.ts`, `interfaces.ts`, `constants.ts`.

The unifying principle: **a file that belongs to a component carries the component's name and casing; a folder that merely groups files uses kebab-case.**

## Implementation rules

1. Create a `@ComponentName/` directory for a component's children.
2. Import children by relative path: `./@ComponentName/Child` or `./@ComponentName/child-folder/Child`.
3. Colocate all component-specific support files (styles, hooks, types) inside the component's folder.
4. Read context in the component that needs it — avoid prop drilling by placing the consumer at the right level.
5. Name components to reflect their role in the UI hierarchy.
6. Follow the naming conventions above: `@`-folders and style files take the component's exact PascalCase name; wrapper component folders are kebab-case.
7. Add a brief comment when a component tree is complex enough that the structure isn't self-evident.

### How this fits the repo's existing conventions

This pattern refines — it does not replace — the repo's file-organization rules in the `playbook-tempo-patterns` skill (`references/modularity.md`). Those still hold: one responsibility per file, keep components small, types live beside services in `@domain/{feature}`, expose multiple symbols through an `index.ts` barrel, and default to unexported. Page-local component trees using this nesting pattern belong under `src/pages/{area}/{feature}/components/`; genuinely reusable primitives still follow the Pulse boundary (`references/pulse-boundary.md`).

## Preserving render order with `.sorting` files

File explorers list files alphabetically by default, which rarely matches the order children actually render or depend on each other. Drop a `.sorting` file into a `@`-folder to tell the File Nesting Explorer the meaningful order.

Given this parent:

```tsx
export const MyComponent = () => (
<div>
<Header />
<Content />
<ActionBar />
</div>
);
```

Alphabetical order would show `ActionBar, Content, Header` — backwards from how they render. Add `@MyComponent/.sorting`:

```json
["Header.tsx", "Content.tsx", "ActionBar.tsx"]
```

…and the explorer shows them in render order.

### What order to encode

The `.sorting` array should reflect either **render order** (the sequence in the JSX) or **dependency flow** (foundational modules first, consumers last).

Hooks, from base to derived to consumer (`useProcessedData` uses `useBaseData`; `Component` uses `useProcessedData`):

```json
["useBaseData.ts", "useProcessedData.ts", "Component.tsx"]
```

Contexts, from provider to consumer (`UserContext` depends on `AuthContext`):

```json
["AuthContext.ts", "UserContext.ts", "Component.tsx"]
```

A mixed dependency chain — `useAuth``AuthContext``useUserData``UserProfile`:

```json
["useAuth.ts", "AuthContext.ts", "useUserData.ts", "UserProfile.tsx"]
```

### `.sorting` rules

1. JSON array of filenames **and folders**. Files keep their extension; folders are listed by bare name with **no trailing slash** — this is how the repo's existing `.sorting` files are written (e.g. `src/pages/business/services/.sorting` lists `"hooks"`, `"components"`, `"modal"` alongside `"ServicesPage.tsx"`), and the explorer matches entries by exact string.
2. Include all direct children of the directory.
3. Order components by JSX render sequence; order utilities/hooks/contexts from foundation to consumer.
4. Keep it in sync when adding or removing files.
5. Each nested level that needs custom order gets its own `.sorting`.

### When to create one

Create a `.sorting` file when order carries meaning: a visual flow (Header → Content → Footer), an interaction sequence (Step1 → Step2 → Step3), a logical grouping (Navigation → Main → Sidebar), or a dependency chain (hook → hook → component, base context → derived context → consumer).

Skip it for unordered collections (list items, grid cells), when alphabetical is already meaningful, when there are only 1–2 children, or when no clear order exists.

## Benefits recap

Clear hierarchical organization, easy-to-locate functionality, self-contained and reusable components, simpler `React.memo`/optimization targeting, fewer merge conflicts and clearer ownership, and an intuitive tree view via the File Nesting Explorer.

Prompt CHANGELOG

  • 2026-02-03: Initial release
  • 2026-08-16: Fixed nesting examples