Skip to main content
skill CX Codex

compound-pattern — Claude Code skill: compound components

Teaches the agent the compound component pattern for shared implicit state. Useful for tabs, accordions and dropdowns whose parts must coordinate without explicit props.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add PatternsDev/skills --skill compound-pattern -a claude-code
Open on GitHub

What compound-pattern does

The skill covers the compound component pattern in React. A parent holds the state and hands it to children through the Context API, while the parts attach as static properties: FlyOut.Toggle, FlyOut.List, FlyOut.Item. The worked example is a FlyOut with a toggle button and a list holding Edit and Delete items.

Inside you get a "When to Use" section, a "When NOT to Use" section, instructions and two approaches: Context API, and React.Children.map with cloned children. The second one passes open and toggle straight into the cloned elements, no context involved. The author prefers Context because it handles nesting better, and suggests memoizing context values so complex trees do not re-render for nothing.

The limits are spelled out. If the sub-components share no meaningful state, Context just adds overhead. For simple one-off UIs a single component with props reads clearer. And implicit state sharing can make behaviour hard to predict for whoever consumes the component. The skill activates on **/*.tsx and **/*.jsx files.

What it helps with

  • build tabs where panels know the active tab without prop drilling
  • ship a dropdown with a toggle button and a list of items
  • expose a clean component API and keep state management internal
  • pick between Context API and React.Children.map for nested children
  • cut extra re-renders in a compound component by memoizing context

Example prompts for the agent

  • Build a tabs component with the compound-pattern approach: parent holds state, panels read it from context
  • Refactor this dropdown into a compound component with Toggle and List as static properties
  • Check my accordion — should this use Context instead of passing props down?

How to install compound-pattern

1Skills CLI (Vercel)

npx skills add PatternsDev/skills --skill compound-pattern -a claude-code

2Manually: put the files in place

~/.claude/skills/compound-pattern/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/compound-pattern/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about compound-pattern

How do I install compound-pattern in Claude?

Run npx skills add PatternsDev/skills --skill compound-pattern -a claude-code. The skill lands in ~/.claude/skills/compound-pattern/SKILL.md.

What does compound-pattern do?

It explains the compound component pattern: a parent keeps shared state and exposes it to children through Context, with the parts attached as static properties. The agent applies it when building tabs, menus and accordions.

When should I avoid this pattern?

When the sub-components share no meaningful state, Context only adds overhead. For simple one-off UIs a single component with props is clearer.

Source text

SKILL.md · MIT
---
name: compound-pattern
description: Teaches the compound component pattern for shared implicit state. Use when building related components like tabs, accordions, or dropdowns that need to coordinate without explicit prop passing.
paths:
  - "**/*.tsx"
  - "**/*.jsx"
license: MIT
metadata:
  author: patterns.dev
  version: "1.1"
related_skills:
  - "hooks-pattern"
  - "hoc-pattern"
---

# Compound Pattern

## Table of Contents

- [When to Use](#when-to-use)
- [When NOT to Use](#when-not-to-use)
- [Instructions](#instructions)
- [Details](#details)
- [Source](#source)

In our application, we often have components that belong to each other. They're dependent on each other through the shared state, and share logic together. You often see this with components like `select`, dropdown components, or menu items. The **compound component pattern** allows you to create components that all work together to perform a task.

## When to Use

- Use this when building components like dropdowns, tabs, or menus with related sub-components
- This is helpful when you want to provide a clean component API without exposing internal state management

## When NOT to Use

- When the sub-components don't share meaningful state — the pattern adds unnecessary Context overhead
- For simple one-off UIs where a single component with props is clearer
- When the implicit state sharing makes the component behavior hard to predict for consumers

## Instructions

- Use React Context API to share state between the parent compound component and its children
- Attach child components as static properties on the parent (e.g., `FlyOut.Toggle`, `FlyOut.List`)
- Memoize context values to avoid unnecessary re-renders in complex scenarios
- Prefer the Context approach over `React.Children.map` for more flexible component nesting

## Details

### Context API

Let's look at an example: we have a list of squirrel images! Besides just showing squirrel images, we want to add a button that makes it possible for the user to edit or delete the image. We can implement a `FlyOut` component that shows a list when the user toggles the component.

Within a `FlyOut` component, we essentially have three things:

- The `FlyOut` wrapper, which contains the toggle button and the list
- The `Toggle` button, which toggles the `List`
- The `List`, which contains the list of menu items

Using the Compound component pattern with React's [Context API](https://reactjs.org/docs/context.html) is perfect for this example!

First, let's create the `FlyOut` component. This component keeps the state, and returns a `FlyOutProvider` with the value of the toggle to all the children it receives.

```js
const FlyOutContext = createContext();

function FlyOut(props) {
  const [open, toggle] = useState(false);

  return (
    <FlyOutContext.Provider value={{ open, toggle }}>
      {props.children}
    </FlyOutContext.Provider>
  );
}
```

We now have a stateful `FlyOut` component that can pass the value of `open` and `toggle` to its children!

Let's create the `Toggle` component. This component simply renders the component on which the user can click in order to toggle the menu.

```js
function Toggle() {
  const { open, toggle } = useContext(FlyOutContext);

  return (
    <div onClick={() => toggle(!open)}>
      <Icon />
    </div>
  );
}
```

In order to actually give `Toggle` access to the `FlyOutContext` provider, we need to render it as a child component of `FlyOut`! We can also make the `Toggle` component a property of the `FlyOut` component!

```js
const FlyOutContext = createContext();

function FlyOut(props) {
  const [open, toggle] = useState(false);

  return (
    <FlyOutContext.Provider value={{ open, toggle }}>
      {props.children}
    </FlyOutContext.Provider>
  );
}

function Toggle() {
  const { open, toggle } = useContext(FlyOutContext);

  return (
    <div onClick={() => toggle(!open)}>
      <Icon />
    </div>
  );
}

FlyOut.Toggle = Toggle;
```

This means that if we ever want to use the `FlyOut` component in any file, we only have to import `FlyOut`!

```js
import React from "react";
import { FlyOut } from "./FlyOut";

export default function FlyoutMenu() {
  return (
    <FlyOut>
      <FlyOut.Toggle />
    </FlyOut>
  );
}
```

Just a toggle is not enough. We also need to have a `List` with list items, which open and close based on the value of `open`.

```js
function List({ children }) {
  const { open } = React.useContext(FlyOutContext);
  return open && <ul>{children}</ul>;
}

function Item({ children }) {
  return <li>{children}</li>;
}
```

The `List` component renders its children based on whether the value of `open` is `true` or `false`. Let's make `List` and `Item` a property of the `FlyOut` component, just like we did with the `Toggle` component.

```js
const FlyOutContext = createContext();

function FlyOut(props) {
  const [open, toggle] = useState(false);

  return (
    <FlyOutContext.Provider value={{ open, toggle }}>
      {props.children}
    </FlyOutContext.Provider>
  );
}

function Toggle() {
  const { open, toggle } = useContext(FlyOutContext);

  return (
    <div onClick={() => toggle(!open)}>
      <Icon />
    </div>
  );
}

function List({ children }) {
  const { open } = useContext(FlyOutContext);
  return open && <ul>{children}</ul>;
}

function Item({ children }) {
  return <li>{children}</li>;
}

FlyOut.Toggle = Toggle;
FlyOut.List = List;
FlyOut.Item = Item;
```

We can now use them as properties on the `FlyOut` component! In this case, we want to show two options to the user: **Edit** and **Delete**. Let's create a `FlyOut.List` that renders two `FlyOut.Item` components, one for the **Edit** option, and one for the **Delete** option.

```js
import React from "react";
import { FlyOut } from "./FlyOut";

export default function FlyoutMenu() {
  return (
    <FlyOut>
      <FlyOut.Toggle />
      <FlyOut.List>
        <FlyOut.Item>Edit</FlyOut.Item>
        <FlyOut.Item>Delete</FlyOut.Item>
      </FlyOut.List>
    </FlyOut>
  );
}
```

Perfect! We just created an entire `FlyOut` component without adding any state in the `FlyOutMenu` itself!

The compound pattern is great when you're building a component library. You'll often see this pattern when using UI libraries like [Semantic UI](https://react.semantic-ui.com/modules/dropdown/#types-dropdown).

### `React.Children.map`

We can also implement the Compound Component pattern by mapping over the children of the component. We can add the `open` and `toggle` properties to these elements, by cloning them with the additional props.

```js
export function FlyOut(props) {
  const [open, toggle] = React.useState(false);

  return (
    <div>
      {React.Children.map(props.children, (child) =>
        React.cloneElement(child, { open, toggle })
      )}
    </div>
  );
}
```

All children components are cloned, and passed the value of `open` and `toggle`. Instead of having to use the Context API like in the previous example, we now have access to these two values through `props`.

### Pros

Compound components manage their own internal state, which they share among the several child components. When implementing a compound component, we don't have to worry about managing the state ourselves.

When importing a compound component, we don't have to explicitly import the child components that are available on that component.

```js
import { FlyOut } from "./FlyOut";

export default function FlyoutMenu() {
  return (
    <FlyOut>
      <FlyOut.Toggle />
      <FlyOut.List>
        <FlyOut.Item>Edit</FlyOut.Item>
        <FlyOut.Item>Delete</FlyOut.Item>
      </FlyOut.List>
    </FlyOut>
  );
}
```


### Cons

When using the `React.Children.map` to provide the values, the component nesting is limited. Only _direct children_ of the parent component will have access to the `open` and `toggle` props, meaning we can't wrap any of these components in another component.

```js
export default function FlyoutMenu() {
  return (
    <FlyOut>
      {/* This breaks */}
      <div>
        <FlyOut.Toggle />
        <FlyOut.List>
          <FlyOut.Item>Edit</FlyOut.Item>
          <FlyOut.Item>Delete</FlyOut.Item>
        </FlyOut.List>
      </div>
    </FlyOut>
  );
}
```

Cloning an element with `React.cloneElement` performs a shallow merge. Already existing props will be merged together with the new props that we pass. This could end up in a naming collision, if an already existing prop has the same name as the props we're passing to the `React.cloneElement` method. As the props are shallowly merged, the value of that prop will be overwritten with the latest value that we pass.

## Source

- [patterns.dev/react/compound-pattern](https://patterns.dev/react/compound-pattern)

### References

- [Render Props - React](https://reactjs.org/docs/render-props.htm)
- [React Hooks: Compound Components - Kent C. Dodds](https://kentcdodds.com/blog/compound-components-with-react-hooks)

© patterns.dev · MIT · PatternsDev/skills

Similar skills

Everything for Frontend and UI →

From the same set44

hooks-pattern A skill that teaches your agent React Hooks and helps pull repeated stateful logic into custom hooks. Useful when components start duplicating behaviour. CX Codex +1 1,100installs render-props-pattern A skill that walks through the render props pattern in React and tells you when a hook is the better call. Useful when rendering logic has to be shared across components. CX Codex +1 1,000installs client-side-rendering A skill that walks through client-side rendering in React: when CSR fits, and when your page loses on SEO and first paint. CX Codex +1 1,000installs hoc-pattern Teaches your AI assistant the Higher-Order Component pattern to share logic across React components. Useful for reusable auth, logging, or data fetching. CX Codex +1 1,000installs react-server-components A skill that teaches React Server Components: which components belong on the server and where 'use client' goes. Useful in Next.js App Router. CX Codex +1 954installs progressive-hydration A skill that tells the agent how to split hydration of a server-rendered React app and defer JavaScript for non-critical sections. CX Codex +1 952installs react-selective-hydration Guides your coding agent in setting up selective hydration and streaming SSR in React 18+. It makes interactive parts responsive before the entire JavaScript bundle downloads. CX Codex +1 943installs server-side-rendering A skill that walks your agent through server-side rendering in React and tells you when SSR pays off and when static or client rendering is the better call. CX Codex +1 942installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor