| name | split-react-components |
| description | Split React/TSX files containing multiple inlined components into separate single-responsibility files. Use when asked to "split components", "extract components", "single responsibility", "clean up component file", "too many components in one file", "component slop", or when running deslop/cleanup on React code. |
| disable-model-invocation | true |
Split React Components
One file, one component, its own types, its own hooks. Anything else is slop.
What counts as slop
- Inline sub-components defined inside another component's function body. These re-create on every render and bloat the host file. Even if they're small.
- SVG icon components inlined alongside the component that uses them. These belong in a dedicated icons file.
- Large string literals / data constants (SVG markup, config arrays, static data) taking more than ~5 lines.
- Interfaces/types that only exist to serve an extracted component — they travel with it.
- Hooks that serve a single sub-component — extract alongside it.
Do NOT split
- Tiny styled wrappers (< 5 lines) tightly coupled to the parent's markup.
- Next.js app router files in
apps/docs only (loading.tsx, error.tsx, layout.tsx).
- Test files.
Workflow
- Scan — Read the target file(s). List every component definition (named functions, arrow components,
forwardRef wrappers).
- Classify — Primary export vs. inlined sub-component/helper.
- Plan — For each extraction:
- Destination path (follow project conventions — kebab-case filenames, named exports).
- Which imports, types, constants move with it.
- What the parent needs to import after extraction.
- Execute — Create new files, remove inlined definitions, add imports in parent.
- Verify — No circular imports. Run lints on all modified files.
File naming
- Match project conventions: kebab-case filenames, named exports (
export const ComponentName).
- Icon components → sibling
icons.tsx or icons/ subfolder.
- Data constants → sibling file named after the constant (e.g.
logo-svg.ts).
- Types → co-locate with the component that owns them.
Example
Before — logo-dropdown.tsx (167 lines, 5 components in one file):
import { FC, useState } from 'react';
interface DropdownItem { }
interface DropdownSection { }
export const LogoDropdown: FC = () => {
const logoSvg = `<svg>...12 lines...</svg>`;
const GitHubIcon: FC<{ className?: string }> = ({ className }) => (
<svg className={className}>...</svg>
);
const LinkedInIcon: FC<{ className?: string }> = ({ className }) => (...);
const TwitterIcon: FC<{ className?: string }> = ({ className }) => (...);
const StackOverflowIcon: FC<{ className?: string }> = ({ className }) => (...);
const sections = [];
return <div>{/* renders sections */}</div>;
};
After — 3 files:
social-icons.tsx (pure, no state):
import { FC } from 'react';
export const GitHubIcon: FC<{ className?: string }> = ({ className }) => (
<svg className={className}>...</svg>
);
export const LinkedInIcon: FC<{ className?: string }> = ({ className }) => (...);
export const TwitterIcon: FC<{ className?: string }> = ({ className }) => (...);
export const StackOverflowIcon: FC<{ className?: string }> = ({ className }) => (...);
logo-svg.ts (data, not a component):
export const logoSvg = `<svg>...</svg>`;
logo-dropdown.tsx (just the stateful component):
import { FC, useState } from "react";
import {
GitHubIcon,
LinkedInIcon,
TwitterIcon,
StackOverflowIcon,
} from "./social-icons";
import { logoSvg } from "./logo-svg";
interface DropdownItem {
}
interface DropdownSection {
}
export const LogoDropdown: FC = () => {
return <div>{/* renders sections */}</div>;
};
Batch mode
When asked to scan a directory or the whole project:
- Glob for
**/*.tsx files.
- For each file, count component definitions. Flag files with 2+ components where at least one is not the primary export.
- Present a summary:
| File | Components | Extractable | Reason |
- Ask before executing, or execute immediately if the user said to fix them all.