Building from the Box
Use the versatile Box as your component foundation.
This a follow up post exploring how the power of the
sx
If you aren't familiar with the
sx
The humble Box
The Box component is used primarily as a layout primitive to add margin, padding, and colors to content.
In the last post, we implemented a minimal Box component that has the powers of the
sx
/** * Simplified modification of the Theme UI Box * http://bit.ly/3pzPiAr */ import styled from '@emotion/styled'; import { css } from '@theme-ui/css'; // Parse the style objects passed via `sx` to the `css(...)` function const sx = (props) => css(props.sx)(props.theme); // Similar to `sx`, but used for "internal" base component styles const base = (props) => css(props.__css)(props.theme); // 👆 This makes adding `sx` style overrides easier // for complex selectors like focus, hover, etc. const Box = styled.div( { boxSizing: 'border-box', margin: 0, minWidth: 0, }, base, sx, (props) => props.css ); export default Box;
In this version, our Box will also have a private
__css
sx
The base styles passed via the
__css
sx
sx
__css
A quick comparison
We can use both the
sx
__css
css
css
/** @jsx jsx */ // import { jsx } from '@emotion/react' // Using the `css` prop from Emotion const CssPropExample = () => ( <div css={{ backgroundColor: 'hotpink', '&:hover': { color: 'lightgreen' } }} > This has a hotpink background. </div> ); render(<CssPropExample />);
The same result can be created using the Box component, but without the need to import our JSX pragma and with the enhanced features of the
sx
/** @jsx React.createElement */ // Reset pragma to React.createElement // Needed after the above example ☝️ // Same result but using our Box component const BoxExample = () => ( <Box sx= {{ backgroundColor: 'hotpink', '&:hover': { color: 'lightgreen' } }}> This has a hotpink background. </Box> ); render(<BoxExample />);
Creating new components
By integrating the power of the
sx
export const Label = React.forwardRef((props, ref) => ( <Box ref={ref} as="label" __css={{ fontSize: 2, fontWeight: 'heading', }} {...props} /> )); export const Text = React.forwardRef((props, ref) => ( <Box ref={ref} __css={{ fontSize: 1, fontWeight: 'body', }} {...props} /> )); export const TextInput = React.forwardRef((props, ref) => ( <Box ref={ref} as="input" type="text" __css={{ bg: 'background', border: '1px solid', borderColor: 'border', borderRadius: 'md', color: 'text', px: 3, py: 2, fontSize: 1, '&:focus': { boxShadow: ({ colors }) => `0 0 0 2px ${colors.outline}`, }, }} {...props} /> ));
We use
React.forwardRef
ref
ref
If you are curious, check out the React documentation for more on forwarding refs.
Lastly, we will need some theme values to define our design constraints. Here is a simple, sample theme object based on the Theme UI Base Preset.
// modified example base theme from @theme-ui/presets export const theme = { space: [0, 4, 8, 16, 32, 64, 128, 256, 512], fontSizes: [12, 14, 16, 20, 24, 32, 48, 64, 96], fontWeights: { body: 400, heading: 600, bold: 700, }, colors: { text: 'var(--colors-text)', background: 'var(--colors-background)', primary: 'var(--colors-primary)', secondary: 'var(--colors-secondary)', muted: 'var(--colors-gray600)', border: 'var(--colors-gray400)', outline: 'var(--colors-gray300)', }, radii: { none: '0', sm: '0.125rem', md: '0.375rem', lg: '0.5rem', full: '9999px', }, };
Just a quick note on the color values used. I'm reusing some already defined CSS custom properties that are being used for this site, e.g.
var(--colors-text)
Theme UI does have a color modes feature which you can also leverage if your application needs to support multiple modes.
Putting them to use
The above Box-based components all have the
sx
Now that we have some bits of UI, let's compose a name input for our application.
const NameInput = () => ( <ThemeProvider theme={theme}> <Label htmlFor="name"> Name </Label> <Text as="p" id="name-helper" sx={{ // Theme-based overrides mb: 2, color: "muted" }}> First and last names </Text> <TextInput id="name" name="name" aria-describedby="name-helper" placeholder="e.g. Jane Doe" /> </ThemeProvider> ); render(<NameInput />);
First and last names
Wrapping up
In summary, the Box is a versatile foundation component that can be using as a building-block to create other components. Adding the flexible styling feature of the
sx
Note: This technique is mostly experimental and an exploration of the component styling pattern used by Theme UI. Applying this pattern at scale for a large component library would need some added refinement and probably best to do in TypeScript
💙
If this was interesting, I encourage you to check out the Theme UI source code for a more detailed look:
Here is a full example in CodeSandbox of the above all pieced together.
Subscribe to the newsletter
Be the first to know when I post something new! Thoughts about code, design, startups and other interesting things.