Layout Components and the Split Screen Pattern

I kept mixing layout CSS into content components until I saw why that kills reusability. Layout components fix this by owning the 'where', so content only owns the 'what'.

June 27, 20263 min read1 / 3

The thing nobody tells you when you first build React components is that you are making two decisions at once. You decide what to render and where it lives on the page. Those two decisions sit in the same file, tangled together, and nobody notices until you try to reuse the component somewhere else.

Layout components separate those decisions. The content component owns what it renders. The layout component owns where everything sits. This is the first of three layout patterns in this series. The series overview has the full map if you want context on where it fits.

What "Layout Component" Actually Means

A layout component holds no content of its own. It only manages spatial arrangement: the flex container, the columns, the positioning. It accepts other components as children or props and places them without knowing anything about them.

The core rule: content components should not know where they are on the page.

A ProfileCard should render identically in a sidebar, a modal, or a three-column grid. If changing its location requires editing the component, the layout concern has leaked into the content.

Building the Split Screen

The split screen is the clearest layout component to build first. It splits the viewport into two panels and places one component on each side.

JSX
const SplitScreen = ({ left: Left, right: Right }) => { return ( <Container> <Panel><Left /></Panel> <Panel><Right /></Panel> </Container> ); }; const Container = styled.div` display: flex; `; const Panel = styled.div` flex: 1; `;

Left and Right are capitalized because they are components, not strings. Container gets display: flex. Each Panel gets flex: 1, giving both sides equal width.

Usage looks like this:

JSX
const NavPanel = () => <nav>Navigation here</nav>; const ContentPanel = () => <main>Content here</main>; <SplitScreen left={NavPanel} right={ContentPanel} />

Adding Width Control

Equal halves are not always what you want. Add leftWidth and rightWidth props so the caller controls the ratio. Default values of 1 mean the component never crashes when those props are omitted.

JSX
const SplitScreen = ({ left: Left, right: Right, leftWidth = 1, rightWidth = 1, }) => { return ( <Container> <Panel flex={leftWidth}><Left /></Panel> <Panel flex={rightWidth}><Right /></Panel> </Container> ); }; const Panel = styled.div` flex: ${p => p.flex}; `;

Calling <SplitScreen leftWidth={1} rightWidth={3} /> gives the right panel 75% of the space. The styled-component reads the flex prop directly from the template literal.

The Children Pattern is Cleaner

Passing components as named props works, but there is a problem. If you want to give NavPanel its own props, you have to go through SplitScreen:

JSX
// awkward <SplitScreen left={() => <NavPanel activeItem="home" />} right={ContentPanel} />

The children pattern solves this. Pass the sub-components directly between the tags and destructure children:

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

Now the call site is clean, and each sub-component receives its own props without SplitScreen knowing they exist:

JSX
<SplitScreen leftWidth={1} rightWidth={3}> <NavPanel activeItem="home" /> <ContentPanel title="Dashboard" /> </SplitScreen>

SplitScreen stays completely ignorant of what it contains. That is what makes it a real layout component and not just a wrapper.

The next pattern in this series applies the same thinking to lists, where the shape of what you are rendering changes from row to row.

The Essentials

  1. A layout component owns the "where"; a content component owns the "what". They should not mix.
  2. Content components should render identically regardless of their location on the page.
  3. The children pattern is cleaner than named props when sub-components need their own props.
  4. Default values on leftWidth and rightWidth prevent crashes when props are omitted.
  5. Styled-component template literals can read props directly with ${p => p.flex}.

Further Reading and Watching

Practice what you just read.

Layout Component QuizBuild a Reusable Split Screen
2 exercises