styled-components Responsive Array Syntax
In this post Iām going to discuss a new approach Iāve adopted when using [styled-components](https://Styled Components.com/), initially I wasnāt sure what this approach was called but I do really like this suggestion from Pedro Duarte at Modulz
I call it responsive array syntax
— Pedro Duarte (@peduarte) August 19, 2020
From the outside when you use the Box component you can provide a width
prop but instead of it simply being a string
value itās an array of values. Reading from left to right this will translate to the following:
- 0: The Box will be
width: 100%;
on small screens - 1: The Box will be
width: 50%;
on medium screens - 2: The Box will be
width: auto;
on large screens
NB: This is assuming in your application you have a set of breakpoints defined, for example:
const breakpoints = ['576px', '768px', '992px'];
As my Tweet mentions both Chakra UI and Theme UI come with this kind of functionality built in, Styled Components does not. Thatās not a bad thing, styled-components is different in many ways to Chakra UI and Theme UI, but rather than try to explain it myself hereās the descriptions from each of the projects.
Chakra UI
āChakra UI is a simple, modular and accessible component library that gives you all the building blocks you need to build your React applicationsā
Theme UI
āTheme UI is a library for creating themeable user interfaces based on constraint-based design principlesā
styled-components
āVisual primitives for the component age. Use the best bits of ES6 and CSS to style your apps without stress š š¾ā
The way I like to think about this is styled-components gives you the tools you need to build everything yourself where as Chakra UI and Theme UI give you the tools you need to build everything yourself + a load of stuff thatās already built for you and can easily be re-themed.
If youāre using styled-components and like the look of the Responsive Array Syntax Iām going to explain how to write a utility function that you can gradually adopt without breaking anything.
Before I go too much further when I use styled-components I donāt use template literals which is what youāll see in the docs, instead I prefer to use the style object syntax.
Thereās a PR for the docs site I created in Mar 2019 that attempts to explain the style object syntax which you can see here. I think I still need to do some work on that š¬ ā¦ so in the meantime Iāve prepared a short post with the same usage examples: styled-components Style Objects
Ok, letās start!
1.
Create a test component, Iāve called mine <MrButton />
because Iām a laugh.
// mr-button.js
import styled from 'styled-components';
const MrButton = styled.button({
backgroundColor: 'hotpink',
border: 'none',
color: 'white',
padding: 8,
width: '100%',
});
export default MrButton;
2.
Make sure youāve implemented <MrButton />
somewhere in your app and are passing in an array of values via the
backgroundColor
and width
props.
// app.js
<MrButton backgroundColor={['hotpink', 'mouve', 'purple']} width={['100%', '50%', 'auto']}>
Click Me
</MrButton>
3.
Create a util function somewhere and call it create-media-queries.js
// create-media-queries.js
export const createMediaQueries = (css) => {
console.log(JSON.stringify(css, null, 2));
};
The first step is to make sure youāre able to pass the backgroundColor
and width
props through the styled component
and on to the function. As mentioned above I prefer to use the style object syntax because I find it easier to
destructure the props and to spread the return of the createMediaQueries
function into the styles šŗ
...
// mr-button.js
import styled from 'styled-components'
+ import { createMediaQueries } from '../utils'
const MrButton = styled.button(
{
- backgroundColor: "hotpink",
color: "white",
border: "none",
padding: 8,
- width: 100%
},
+ ({ backgroundColor, width }) => ({
+ ...createMediaQueries([
+ {
+ property: "background-color",
+ values: backgroundColor
+ },
+ {
+ property: "width",
+ values: width
+ }
+ ])
+ })
);
export default MrButton;
If youāve followed the steps above you should see a console.log
like this: š
[
{
property: 'background-color',
values: ['hotpink', 'mouve', 'purple'],
},
{
property: 'width',
values: ['100%', '50%', 'auto'],
},
];
Now youāve confirmed the values passed in via the props get through the styled component and on to the
createMediaQueries
function you can do something with them, but before I move on letās define an array of breakpoints.
You could put this array anywhere in your project but just for demoās sake Iāll add it to the top of the
create-media-queries.js
file.
// create-media-queries.js
+ const breakpoints = [576, 768, 992]
export const createMediaQueries = (css) => {
console.log(JSON.stringify(css, null, 2));
}
The aim now is to create key values pairs for each property and each of the values, youāll also need to catch any values
passed in that arenāt an array by using Array.isArray()
and rather than .map
them just return a key value pair.
// create-media-queries.js
const breakpoints = [576, 768, 992]
export const createMediaQueries = (css) => {
- console.log(JSON.stringify(css, null, 2));
+ const cssKeyValuePairs = css.reduce((items, item) => {
+ const { property, values } = item;
+ items.push(
+ Array.isArray(item.values)
+ ? values.map((value) => ({
+ [property]: value
+ }))
+ : [{ [property]: values }]
+ );
+ return items;
+ }, []);
+ console.log(JSON.stringify(cssKeyValuePairs, null, 2));
}
Which should give you a console.log
that looks like this: š
[
[
{
'background-color': 'hotpink',
},
{
'background-color': 'mouve',
},
{
'background-color': 'purple',
},
],
[
{
width: '100%',
},
{
width: '50%',
},
{
width: 'auto',
},
],
];
The next part of the function will be responsible for assigning each of those values to an appropriate breakpoint value
// create-media-queries.js
const breakpoints = [576, 768, 992]
export const createMediaQueries = (css) => {
const cssKeyValuePairs = css.reduce((items, item) => {
const { property, values } = item;
items.push(
Array.isArray(item.values)
? values.map((value) => ({
[property]: value
}))
: [{ [property]: values }]
);
return items;
}, []);
- console.log(JSON.stringify(cssKeyValuePairs, null, 2));
+ const cssToBreakpoints = [0, ...breakpoints]
+ .map((breakpoint, index) => ({
+ breakpoint: breakpoint,
+ css: cssKeyValuePairs
+ .map((array) => array[index])
+ .filter(Boolean)
+ .reduce((items, item) => {
+ items[`${Object.keys(item)}`] = `${Object.values(item)}`;
+ return items;
+ }, {})
+ }))
+ .slice(0, -1);
+ console.log(JSON.stringify(cssToBreakpoints, null, 2));
}
Which should give you a console.log
that looks like this: š
[
{
"breakpoint": 0,
"css": {
"background-color": "hotpink",
"width": "100%"
}
},
{
"breakpoint": 576,
"css": {
"background-color": "mouve",
"width": "50%"
}
},
{
"breakpoint": 768,
"css": {
"background-color": "purple",
"width": "auto"
}
}
]
ā
Thereās a couple of things this part of the function does so Iāll talk through them.
[0, ...breakpoints];
The above creates a new array and inserts a 0
at the beginning. Youāll need the extra value at the start because
youāre developing this function to be mobile first. The first value from the values array is the default style and
isnāt part of a media query
breakpoint: breakpoint;
The above creates a key value pair for the breakpoint value, eg breakpoint: 0,
css: cssKeyValuePairs
.map((array) => array[index])
.filter(Boolean)
.reduce((items, item) => {
items[`${Object.keys(item)}`] = `${Object.values(item)}`;
return items;
}, {});
The above creates a new object called css
and within it are the key values pairs from the first part of the function
.slice(0, -1);
Next youāll need to remove the last item from the array since you shifted all the values up by one by inserting the 0
at the start of the arrayā¦ I knowā¦ mobile first is a bit confusing.
The final part of the function is where you can create formed media queries that will work when used with the style object syntax
// create-media-queries.js
const breakpoints = [576, 768, 992]
export const createMediaQueries = (css) => {
const cssKeyValuePairs = css.reduce((items, item) => {
const { property, values } = item;
items.push(
Array.isArray(item.values)
? values.map((value) => ({
[property]: value
}))
: [{ [property]: values }]
);
return items;
}, []);
const cssToBreakpoints = [0, ...breakpoints]
.map((breakpoint, index) => ({
breakpoint: breakpoint,
css: cssKeyValuePairs
.map((array) => array[index])
.filter(Boolean)
.reduce((items, item) => {
items[`${Object.keys(item)}`] = `${Object.values(item)}`;
return items;
}, {})
}))
.slice(0, -1);
- console.log(JSON.stringify(cssToBreakpoints, null, 2));
+ const cssMediaQueries = cssToBreakpoints.reduce((items, item) => {
+ const { breakpoint, css } = item;
+
+ breakpoint
+ ? (items[`@media screen and (min-width: ${breakpoint}px)`] = {
+ ...css
+ })
+ : (items = { ...css });
+
+ return items;
+ }, {});
+ console.log(JSON.stringify(cssMediaQueries, null, 2));
+ return {
+ ...cssMediaQueries
+ };
}
Which should give you a console.log
that looks like this: š
{
"background-color": "hotpink",
"width": "100%",
"@media screen and (min-width: 576px)": {
"background-color": "mouve",
"width": "50%"
},
"@media screen and (min-width: 768px)": {
"background-color": "purple",
"width": "auto"
}
}
In this step youāll use array reduce again to loop over the cssToBreakpoints
array and āmassageā it into a new shape.
The default css
values are returned outside of a media query and the 2nd and 3rd set of values use the breakpoint
number and return a formed media query with the correct css
values injected
Iām sure thereās a million other ways to solve this problem but in my case it worked a treat. If you have any feedback or suggestions please feel free to find me on Twitter:
Oh and if youād like to dig a little deep hereās a [CodeSandbox](https://codesandbox.io/s/Styled Components-responsive-array-syntax-3qxu3)