Component Style

Writing component styles in a way that is easy to maintain over the life of a growing and changing project is a challenging task.

To solve this, we came up with the idea of style configuration or styleConfig. This is a consistent theming API that makes component styling easy to understand and maintain.

Base styles and Modifier styles#

Most component style consists of base or default styles and modifier styles that alter its size or visual style based on some properties or state.

Common modifier styles includes:

  • Size: A component can have different sizes (e.g. small, medium, large)
  • Variant: A component can have different visual styles (e.g. outline, solid, ghost)
  • Color scheme: For a given variant, a component can have different color schemes (e.g. an outline button with a red color scheme)
  • Color mode: A component can change its visual styles based on color mode (e.g. light or dark).

Single part and multipart components#

Most components we build today are either single part components (e.g. Button, Badge) or multipart components (e.g. Tabs, Menu, Modal).

A single part component is a component that returns a single element. For example, the <Button> component renders a <button> HTML element:

// This component renders only one element (<button>)
<Button>My button</Button>

A multipart component is a component that has multiple parts, and require these parts to work correctly. This is commonly referred to as a composite component.

For example, a Tabs component consists of TabList, Tab, TabPanels, and TabPanel. Styling this component as a whole might require styling each component part.

<Tabs>
<TabList>
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
</TabList>
<TabPanels>
<TabPanel>Tab 1</TabPanel>
<TabPanel>Tab 2</TabPanel>
</TabPanels>
</Tabs>

Styling single part components#

The basic API for styling a single part component is:

export default {
// Styles for the base style
baseStyle: {},
// Styles for the size variations
sizes: {},
// Styles for the visual style variations
variants: {},
// The default `size` or `variant` values
defaultProps: {},
}

Let's say we want to create a custom button component following the design spec below.

Here's a contrived implementation of the design:

const Button = {
// The styles all button have in common
baseStyle: {
fontWeight: "bold",
textTransform: "uppercase",
borderRadius: "base", // <-- border radius is same for all variants and sizes
},
// Two sizes: sm and md
sizes: {
sm: {
fontSize: "sm",
px: 4, // <-- px is short for paddingLeft and paddingRight
py: 3, // <-- py is short for paddingTop and paddingBottom
},
md: {
fontSize: "md",
px: 6, // <-- these values are tokens from the design system
py: 4, // <-- these values are tokens from the design system
},
},
// Two variants: outline and solid
variants: {
outline: {
border: "2px solid",
borderColor: "purple.500",
color: "purple.500",
},
solid: {
bg: "purple.500",
color: "white",
},
},
// The default size and variant values
defaultProps: {
size: "md",
variant: "outline",
},
}

Makes sense right? Now, let's update the theme to include this new component style.

import { extendTheme } from "@chakra-ui/react"
const theme = extendTheme({
components: {
Button,
},
})

And that's it! You can use your new Button along with its custom variants throughout your app. But what if we want to create a custom component that's not part of Chakra UI? Let's use the following design spec for a Card component:

Here's a contrived implementation of the design:

const Card = {
// The styles all Cards have in common
baseStyle: {
display: "flex",
flexDirection: "column",
background: "white",
alignItems: "center",
gap: 6,
},
// Two variants: rounded and smooth
variants: {
rounded: {
padding: 8,
borderRadius: "xl",
boxShadow: "xl",
},
smooth: {
padding: 6,
borderRadius: "base",
boxShadow: "md",
},
},
// The default variant value
defaultProps: {
variant: "smooth",
},
}

As with the Button component, we'll update the theme to include the new Card component style.

import { extendTheme } from "@chakra-ui/react"
const theme = extendTheme({
components: {
Card,
},
})

But in this case we'd have to consume these styles because the Card component is not a built-in component in Chakra UI.

Consuming style config#

Since the new Card component is not part of Chakra UI we need to create a new React component and consume the style we just created. We can do that using useStyleConfig hook.

useStyleConfig API#

const styles = useStyleConfig(themeKey, props)

Parameters#

  • themeKey: the key in theme.components that points to the desired styleConfig.
  • props: the options object used to compute the component styles. It typically consists of the size, variant, and colorScheme

Return Value#

The computed styles for the component based on props passed. If no props is passed, the defaultProps defined in the style config will be used.

import { Box, useStyleConfig } from "@chakra-ui/react"
function Card(props) {
const { variant, children, ...rest } = props
const styles = useStyleConfig("Card", { variant })
// Pass the computed styles into the `__css` prop
return <Box __css={styles} {...rest} />
}

Please note that we are passing the styles to the prop __css. It has the same API as the sx prop, but has a lower style priority. This means you can override the style properties with chakra style props.

And lastly - the fun part - let's use our custom Card component anywhere in our app:

// 1. Using the default props defined in style config
function Usage() {
return (
<Card>
<Image
src="https://chakra-ui.com/eric.jpg"
rounded="full"
w={32}
h={32}
boxShadow="md"
/>
<Heading mt={6} maxW={60} size="lg" textAlign="center" color="gray.700">
Welcome back, Eric
</Heading>
<Text mt={6} mb={6} size="sm" color="blackAlpha.500">
Use your fingerprint to continue.
</Text>
<Image src="/fingerprint.png" w={32} h={32} />
</Card>
)
}
// 2. Overriding the default
function Usage() {
return (
<Card variant="smooth">
<Image
src="https://chakra-ui.com/eric.jpg"
rounded="full"
w={32}
h={32}
boxShadow="md"
/>
<Heading mt={6} maxW={60} size="lg" textAlign="center" color="gray.700">
Welcome back, Eric
</Heading>
<Text mt={6} mb={6} size="sm" color="blackAlpha.500">
Use your fingerprint to continue.
</Text>
<Image src="/fingerprint.png" w={32} h={32} />
</Card>
)
}

Styling multipart components#

This is very similar to styling single part components with a few differences you need to be aware of.

  • Given that multipart refers to a component with multiple parts, you'll need to define the parts in a part key in the style config.
  • You'll need to provide styles for each part, baseStyle, sizes, and variants.

Pro tip 💡: If you're looking for a list of parts of a multipart component you can check it by clicking on the "View theme source" button at the top of the documentation page for that certain component. Check out this example.

Here's what the style config for multipart components looks like:

export default {
// The parts of the component
parts: [],
// The base styles for each part
baseStyle: {},
// The size styles for each part
sizes: {},
// The variant styles for each part
variants: {},
// The default `size` or `variant` values
defaultProps: {},
}

For example, here's what the style configurations for a custom menu component looks like:

const Menu = {
parts: ["menu", "item"],
baseStyle: {
menu: {
boxShadow: "lg",
rounded: "lg",
flexDirection: "column",
py: "2",
},
item: {
fontWeight: "medium",
lineHeight: "normal",
color: "gray.600",
},
},
sizes: {
sm: {
item: {
fontSize: "0.75rem",
px: 2,
py: 1,
},
},
md: {
item: {
fontSize: "0.875rem",
px: 3,
py: 2,
},
},
},
defaultProps: {
size: "md",
},
}

Next, we'll update the theme object to included this new component style.

import { extendTheme } from "@chakra-ui/react"
const theme = extendTheme({
components: {
Menu,
},
})

Consuming multipart style config#

Now that the style config is hooked into the theme, we can consume within any component using useMultiStyleConfig hook.

We can also mount the computed styles on a specialized context provider called StylesProvider. These styles will now be available to other sub-components. To read from the context, use the useStyles hook.

useMultiStyleConfig API#

const styles = useMultiStyleConfig(themeKey, props)

Parameters#

  • themeKey: the key in theme.components that points to the desired styleConfig.
  • props: an option of the options for computing the final styles. It typically consists of the size, variant, and colorScheme.

Return Values#

The computed styles for each component part based on size, or variant. If none of these were passed, the defaultProps defined in the styleConfig will be used.

// 1. Import the components and hook
import {
StylesProvider,
useMultiStyleConfig,
useStyles,
} from "@chakra-ui/react"
function Menu(props) {
const { size, variant, children, ...rest } = props
// 2. Consume the `useMultiStyleConfig` hook
const styles = useMultiStyleConfig("Menu", { size, variant })
return (
<Flex __css={styles.menu} {...rest}>
{/* 3. Mount the computed styles on `StylesProvider` */}
<StylesProvider value={styles}>{children}</StylesProvider>
</Flex>
)
}
function MenuItem(props) {
// 4. Read computed `item` styles from styles provider
const styles = useStyles()
return <Box as="button" __css={styles.item} {...props} />
}

That's it! We can use our newly created multipart component in our application:

// 1. Using the default props defined in style config
function Usage() {
return (
<Menu>
<MenuItem>Awesome</MenuItem>
<MenuItem>Sauce</MenuItem>
</Menu>
)
}
// 2. Overriding the default
function Usage() {
return (
<Menu size="sm">
<MenuItem>Awesome</MenuItem>
<MenuItem>Sauce</MenuItem>
</Menu>
)
}

Proudly made inNigeria by Segun Adebayo