Back to home

React Design Patterns: Layout Components

Sep 8, 2026

React Design Patterns: Layout Components

Layout components are React components that organize other components on a web page. They are useful because the components that display content do not need to know exactly where they will appear.

For example, a side navigation component should focus on displaying the navigation. A separate layout component can decide whether that navigation appears on the left, on the right, or inside another page structure. This separation makes components easier to reuse and gives the parent component control over placement and styling.

In this article, we will look at three layout component patterns: split screens, reusable lists and list items, and modals.

What Are Layout Components?

Normally, a component may contain both its HTML structure and the styles that decide where it appears. A layout component uses a different approach. It keeps the layout styles in one component and receives the content components through props or children.

The main idea is simple: a content component should work independently of its location on the page. The parent layout decides how that component is arranged.

This pattern is helpful for layouts such as split screens, lists, and content displayed above the page in a modal.

The Split-Screen Pattern

A split screen places two components in separate sections of the page. The left and right sections are only names for this example. The same pattern can also be used for top and bottom sections or other arrangements.

The examples below use styled-components for styling:

npm install styled-components

Create a SplitScreen component that receives the components to render on each side:

import styled from "styled-components";

const Container = styled.div`
  display: flex;
`;

const Panel = styled.div`
  flex: ${(props) => props.flex};
`;

export const SplitScreen = ({
  left,
  right,
  leftWidth = 1,
  rightWidth = 1,
}) => {
  return (
    <Container>
      <Panel flex={leftWidth}>{left}</Panel>
      <Panel flex={rightWidth}>{right}</Panel>
    </Container>
  );
};

The Container uses flexbox to arrange the panels. The Panel receives a flex prop, which allows the parent to control how much space each side occupies. The default value for both widths is 1, so both panels occupy an equal amount of space when no widths are provided.

We can use the component with two simple content components:

const LeftSide = () => <h2>I am left</h2>;
const RightSide = () => <h2>I am right</h2>;

export default function App() {
  return (
    <SplitScreen
      left={<LeftSide />}
      right={<RightSide />}
      leftWidth={1}
      rightWidth={3}
    />
  );
}

Here, the right panel receives three flex units while the left panel receives one. This lets the right side occupy more space without changing either content component.

Passing Components Through children

The split-screen component can also use React's built-in children prop. Instead of passing left and right as separate props, we can place the components inside SplitScreen:

export const SplitScreen = ({ children }) => {
  const [left, right] = children;

  return (
    <Container>
      <Panel flex={1}>{left}</Panel>
      <Panel flex={1}>{right}</Panel>
    </Container>
  );
};

Then the component can be used like this:

<SplitScreen>
  <LeftSide title="Left" />
  <RightSide title="Right" />
</SplitScreen>

The children prop contains the components placed between the opening and closing tags. This approach is cleaner when the layout contains subcomponents, and those subcomponents can still receive their own props.

Reusable Lists and List Items

Displaying a list can become difficult when the same data needs to appear in different formats. For example, a small author item might display only a name and age, while a large author item might also display a country and a list of books.

The list item components should focus on displaying the data. They should not decide the styling or the type of list where they will be used. The parent list can control those details.

A small author item can be kept simple:

export const SmallAuthorListItem = ({ author }) => {
  const { name, age } = author;

  return (
    <p>
      {name}, age is {age}
    </p>
  );
};

A larger author item can display more information:

export const LargeAuthorListItem = ({ author }) => {
  const { name, age, country, books } = author;

  return (
    <>
      <h2>{name}</h2>
      <p>Age is {age}</p>
      <p>Country is {country}</p>
      <p>Books:</p>
      <ul>
        {books.map((book) => (
          <li key={book}>{book}</li>
        ))}
      </ul>
    </>
  );
};

The components do not contain their own layout styling. This allows the parent component to use them in a page, a numbered list, or another type of list and apply the style needed for that location.

One List Component for Different Data

Instead of creating a separate list component for every data type, we can create a reusable list that receives the items, the source name, and the item component:

export const RegularList = ({ items, sourceName, ItemComponent }) => {
  return (
    <>
      {items.map((item, index) => (
        <ItemComponent
          key={index}
          {...{ [sourceName]: item }}
        />
      ))}
    </>
  );
};

The sourceName value tells the list whether the item should be passed as an author or a book. The spread syntax creates the matching prop dynamically. This avoids hardcoding one data type into the reusable list.

For example, the same list can render authors in small and large formats:

<RegularList
  items={authors}
  sourceName="author"
  ItemComponent={SmallAuthorListItem}
/>

<RegularList
  items={authors}
  sourceName="author"
  ItemComponent={LargeAuthorListItem}
/>

The list can also render books by changing the data and the item component:

<RegularList
  items={books}
  sourceName="book"
  ItemComponent={SmallBookListItem}
/>

<RegularList
  items={books}
  sourceName="book"
  ItemComponent={LargeBookListItem}
/>

The SmallBookListItem and LargeBookListItem components can display different book information while the list component remains the same.

Creating a Numbered List

Once the list and list-item components are separated, another list variation can be composed from the same pieces. A numbered list can add the index before rendering the item component:

export const NumberedList = ({ items, sourceName, ItemComponent }) => {
  return (
    <>
      {items.map((item, index) => (
        <React.Fragment key={index}>
          <h3>{index + 1}</h3>
          <ItemComponent
            {...{ [sourceName]: item }}
          />
        </React.Fragment>
      ))}
    </>
  );
};

The important idea is composition. A regular list, numbered list, small item, and large item can be combined to create different display variations without rewriting the data or the item components.

Building a Modal Component

A modal displays content above the page. It can be built with a component, children, and state instead of adding another dependency.

The modal needs two styled elements: a background that covers the page and a content area that contains the child components.

import { useState } from "react";
import styled from "styled-components";

const ModalBackground = styled.div`
  position: absolute;
  left: 0;
  top: 0;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.5);
`;

const ModalContent = styled.div`
  margin: 20px;
  padding: 20px;
  background: white;
`;

export const Modal = ({ children }) => {
  const [show, setShow] = useState(false);

  return (
    <>
      <button onClick={() => setShow(true)}>Show modal</button>

      {show && (
        <ModalBackground onClick={() => setShow(false)}>
          <ModalContent onClick={(event) => event.stopPropagation()}>
            <button onClick={() => setShow(false)}>Hide modal</button>
            {children}
          </ModalContent>
        </ModalBackground>
      )}
    </>
  );
};

The show state starts as false, so the modal is hidden at the beginning. Clicking the first button changes it to true and displays the modal. Clicking the background changes it back to false.

The modal content stops click propagation. Without this, clicking inside the content would also reach the background's click handler and close the modal. The close button inside the content explicitly hides the modal.

Because the modal receives children, it can display different content without knowing what that content is. For example, a large book item can be placed inside it:

<Modal>
  <LargeBookListItem book={books[0]} />
</Modal>

The same item can still be used in a regular list or a numbered list. The item component does not need to know which layout is using it.

Best Practices Shown by These Patterns

The patterns in this article follow a few connected ideas:

  • Keep layout styles inside layout components.
  • Keep content components independent of their location.
  • Pass components into layouts through props or children.
  • Keep list item components focused on displaying their data.
  • Reuse one list component with different data and item components.
  • Use composition to create regular and numbered list variations.
  • Use modal state to control whether modal content is visible.

The purpose is not to create many components for no reason. In a larger codebase, these small reusable components can be composed into many variations without duplicating the same logic.

Common Problems to Avoid

One problem is placing too much layout styling inside the content component. When a component decides its own exact placement, it becomes less reusable. Keeping that styling in the parent layout gives the component more flexibility.

Another problem is hardcoding the data type inside a reusable list. A list that only knows about authors cannot easily display books. Passing the source name and item component lets the same list work with both.

When displaying an array, each rendered list item also needs a key. The examples use the index to keep the demonstration simple, while a real item can use an available identifier.

For modals, the background and content have different click behavior. The background closes the modal, while the content stops propagation so clicks inside it do not trigger the background handler.

Summary

Layout components organize other components while keeping the content components independent of their placement. The split-screen pattern receives components and controls how much space each one occupies. Reusable list patterns separate the list from the item components, allowing the same data to be displayed in small, large, regular, or numbered formats.

A modal follows the same idea. It owns the visibility state and layout, while the content is passed through children. These patterns make React components more reusable because each component has a clear responsibility and can be composed with other components to create new layouts.