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.buttons
object. The Button component usestheme.buttons.primary
as 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.