Understanding Theme UI: 3 - Components
In this post iām going to explain how to use Theme UIās built in components. If youāre new to Theme UI iād suggest having a read of the first two posts in this series to bring you up to speed
If you have read the first two posts, I must apologize as Iāll now be explaining why I donāt use either of those methods but I will of course be offering up an my preferred approach.
Under the hood things remain broadly the same, itās where we write the Jsx that differs somewhat. To give you a taster of what Iāll be covering I have previously touched upon this subject in a previous post: Everythingās a box
To demonstrate Iāll be creating the same component using three different methods available to you in Theme UI and explain why I prefer the final method which iāll refer to as āthe componentā approachā
The Jsx approach
This approach uses the Jsx Pragma and is mostly what youāll see in the docs.
// MrJsxCard.js
/** @jsx jsx */
import { jsx } from 'theme-ui';
export const MrJsxCard = () => {
return (
<div
sx={{
backgroundColor: 'surface',
}}
>
<img
src='https://placedog.net/600/350'
alt='a dog - woof'
sx={{
boxSizing: 'border-box',
margin: 0,
minWidth: 0,
maxWidth: '100%',
height: 'auto',
}}
/>
<div
sx={{
p: 3,
}}
>
<h4
sx={{
color: 'text',
fontFamily: 'heading',
fontWeight: 'heading',
fontSize: 2,
mt: 0,
mb: 3,
}}
>
MrJsxCard
</h4>
<p
sx={{
color: 'text',
fontFamily: 'body',
fontWeight: 'body',
mt: 0,
mb: 3,
}}
>
Lorem ipsum ...
</p>
<button
sx={{
appearance: 'none',
backgroundColor: 'primary',
border: 0,
color: 'text',
borderRadius: 0,
cursor: 'pointer',
fontSize: 1,
lineHeight: 'inherit',
minWidth: 120,
margin: 0,
textDecoration: 'none',
px: 3,
py: 2,
':focus': {
outline: 'none',
transition: '.2s linear box-shadow',
boxShadow: (theme) => `0 0 0 2px ${theme.colors.muted}`,
},
}}
>
Click Me
</button>
</div>
</div>
);
};
Pretty verbose ay?! Thatās because thereās quite a lot of styles applied via the sx prop. Each mapped CSS property
still looks to the correct part of the theme to find itās appropriate value but this approach can lead to duplicating a
lot of styles between components making re-usability difficult to maintain.
The Styled approach
This approach uses the Styled component which can reduce the verbosity.
// MrStyledCard.js
/** @jsx jsx */
import { jsx, Styled } from 'theme-ui';
export const MrStyledCard = () => {
return (
<div
sx={{
backgroundColor: 'surface',
}}
>
<Styled.img
src='https://placedog.net/600/350'
alt='a dog - woof'
sx={{
boxSizing: 'border-box',
margin: 0,
minWidth: 0,
maxWidth: '100%',
height: 'auto',
}}
/>
<div
sx={{
p: 3,
}}
>
<Styled.h4>MrStyledCard</Styled.h4>
<Styled.p>Lorem ipsum ...</Styled.p>
<button
sx={{
appearance: 'none',
backgroundColor: 'primary',
border: 0,
color: 'text',
borderRadius: 0,
cursor: 'pointer',
fontSize: 1,
lineHeight: 'inherit',
minWidth: 120,
margin: 0,
textDecoration: 'none',
px: 3,
py: 2,
':focus': {
outline: 'none',
transition: '.2s linear box-shadow',
boxShadow: (theme) => `0 0 0 2px ${theme.colors.muted}`,
},
}}
>
Click Me
</button>
</div>
</div>
);
};
By using <Styled.h4 /> and <Styled.p /> we can remove quite a few styles from the <h4 /> and <p /> HTML
elements. This is a perfectly valid way to use Theme UI but personally the mix n match methods of using the Jsx Pragma
and Styled donāt quite sit right with me. The <button /> is still a little verbose looking and would be tricky to
re-use
The Component approach
This approach (my preference) uses Theme UIās built in components. As with CSS properties, components can also āmapā to a specific objects within the theme.
// MrComponentCard.js
import React from 'react';
import { Box, Heading, Text, Button, Card, Image } from 'theme-ui';
export const MrComponentCard = () => {
return (
<Card>
<Image src='https://placedog.net/600/350' alt='a dog - woof' />
<Box
sx={{
p: 3,
}}
>
<Heading as='h4' variant='styles.h4'>
MrComponentCard
</Heading>
<Text as='p' variant='styles.p'>
Lorem ipsum ...
</Text>
<Button>Click Me</Button>
</Box>
</Card>
);
};
Using Theme UIās components keeps things consistent and we no longer need to mix n match methods and, et voilĆ the React import is back which makes this component look much more familiar. I also believe this is much more readable in terms of what each component is, e.g āHeadingā and āTextā and because all components are mapped back to the theme we can hide all their styles away in the theme object.
Only one element has an sx prop, the <Box /> personally I feel itās ok to add the sx props on layout wrappers
for CSS properties like padding, margin and display etc as these vary greatly from component to component. The
styles for common or re-usable components e.g <Heading />, <Text /> and <Button /> have all been neatly abstracted
away.
Iāve mentioned āmappingā a few times throughout these posts and I know from discussions this is a tricky concept to grasp, but, it is covered in the docs if you know where to look.
Hereās what the docs say about the <Button /> component
Button variants can be defined in the
theme.buttonsobject. The Button component usestheme.buttons.primaryas its default variant style.
// path-to-theme/index.js
export default {
colors: {
...
},
fonts: {
...
},
fontWeights: {
...
},
space : {
...
},
styles: {
...
},
buttons: {
primary: {
backgroundColor: 'primary',
color: 'text',
...
':focus' : {
...
}
},
},
}
Hopefully you can see the theme object above contains an object called buttons and within it is another object called
primary. This is the location in the theme object that the <Button /> component will look to by default for itās
styles.
If you wanted to introduce a new variant, e.g āsecondaryā you could do the following
Usage š
<Button variant='secondary'>Click Me</Button>
Theme š
// path-to-theme/index.js
export default {
...
buttons: {
primary: {
...
},
secondary : {
backgroundColor: 'secondary',
color: 'background',
...
':focus' : {
...
}
}
},
}
In this scenario instead of the <Button /> defaulting to theme.buttons.primary we re-map it to
theme.buttons.secondary by applying a variant prop of āsecondaryā
The above introduces a new concept called āvariantsā and variants can be used by components using the variant prop or
by using variant as CSS key from either the sx prop or from within the theme object itself. Iāll be discussing
variants in more depth in a later post so please hang tight whilst I whip up more code snippets and examples.