Skip to content

Chakra UI (v2) - Wrap a Layout Component with Default Styles

Posted on Dec 27, 2023 in Blog Posts


Version Reference

At the time of writing, @chakra-ui/react's version is 2.8.2.

Overview

This post explores how to create wrapper components for Chakra UI layout components. Unlike typical components, layout components can't be customized via theme extension. Layout components include components like HStack, Stack, and VStack.

Why might you want to create reusable components for specific styles? Rather than hard-coding, for example, gap=0 on every Stack, HStack, and VStack component, you can wrap Chakra layout components with your own customized styles and use that component instead. Any time you want to make a change to the gap, you only have to make it in one place instead of finding every reference of gap=0 in Chakra components across your codebase.

Creating Wrapper Components

For this example, let's say we want to add a default gap of 0 for all Stack, HStack, and VStack components. We can create wrapper components that set a default but still allow it to be modified like the regular Chakra layout component.

Creating the Stack Wrapper Component

In the example project, we've created a new folder components in src, and also added a Stacks.tsx file.

First, let's set a constant for a default gap that we can use across all three wrappers.

src/components/Stacks.tsx
const defaultGap = 0;

Next, let's create the framework for a React component.

src/components/Stacks.tsx
export function StackTight(){
return <></>
}

We want the StackTight component to use the Chakra Stack component, so let's import that and its props.

From the component, we want to return the Chakra Stack and any props we pass in. Use destructuring to pull out children, gap, and the rest of the props into ...props.

We'll return the Stack with the rest of the props {...props} wrapping {children}.

src/components/Stacks.tsx
import {Stack, type StackProps} from "@chakra-ui/react";
export function StackTight({children, gap, ...props}: StackProps){
return <Stack gap={gap} {...props}>{children}</Stack>
}

Now, let's set our gap to the defaultGap we set earlier.

src/components/Stacks.tsx
import {Stack, type StackProps} from "@chakra-ui/react";
const defaultGap = 0;
export function StackTight({children, gap = defaultGap, ...props}: StackProps){
return <Stack gap={gap} {...props}>{children}</Stack>
}

Creating the HStack and VStack Wrapper Components

Continuing in the Stacks.tsx file, let's import HStack and HStackProps.

src/components/Stacks.tsx
import {HStack, type HStackProps, Stack, type StackProps} from "@chakra-ui/react";

We can copy/paste the component we created for Stack as a starter for HStack, replacing the component name, prop types, and returned component.

src/components/Stacks.tsx
export function HStackTight({children, gap, ...props}: HStackProps){
return <HStack gap={gap} {...props}>{children}</HStack>
}

We can repeat the same thing for VStack.

Once that's all completed, our Stacks.tsx file looks like this:

src/components/Stacks.tsx
import {HStack, type HStackProps, Stack, type StackProps, VStack, type VStackProps} from "@chakra-ui/react"
const defaultGap = 0;
export function StackTight({children, gap = defaultGap, ...props}: StackProps){
return <Stack gap={gap} {...props}>{children}</Stack>
}
export function VStackTight({children, gap = defaultGap, ...props}: VStackProps){
return <VStack gap={gap} {...props}>{children}</VStack>
}
export function HStackTight({children, gap = defaultGap, ...props}: HStackProps){
return <HStack gap={gap} {...props}>{children}</HStack>
}

Using Wrapped Components

In our App.tsx, let's check out the difference between using Stack and our new StackTight.

We can import Stack from the Chakra package, and import StackTight from our src/components/Stacks.tsx file.

src/App.tsx
import { Link, Stack } from "@chakra-ui/react"
import { StackTight } from "./components/Stacks"
export default function App() {
return (
<main>
<br />
<Stack style={{ border: "1px solid black" }}>
<div>This is a Chakra stack.</div>
<div>Here's another item.</div>
</Stack>
<br />
<StackTight style={{ border: "3px solid black" }}>
<div>This is our tight stack.</div>
<div>Here's another item.</div>
</StackTight>
<br />
</main >
)
}

Check out the results on Replit . You can see that our tight stack has no gap between the two items, whereas the Chakra stack has a 0.5rem gap by default. We can even still pass it props, like style, and they work because we spread the props into the returned Stack component.

Now, anywhere we use our StackTight component, we can easily update the default gap if we decide to change it later.