Write Media Queries Once, Use Everywhere

Media queries are difficult to manage if you don't have a single place in your app where you have them defined. I've worked in many apps where media queries are scattered throughout CSS and components and it's nearly impossible to maintain.
I'm using emotion and I was seeing how to implement media queries on this website. Super late to the game, but I stumbled upon using facepaint to define reusable media queries.
facepaint
takes an array of selectors, in our case media query breakpoints, and returns a function that you wrap
around your object styles.
facepaint(selectors: Array<Selector>) : DynamicStyleFunction
Breakpoints are defined like:
import facepaint from 'facepaint';const mq = facepaint(['@media(min-width: 420px)','@media(min-width: 920px)','@media(min-width: 1120px)',]);
Then you can use mq
to generate media queries for a specific property:
mq({ color: ['red', 'green', 'blue'] });
💁 Remember, facepaint only works with object styles!
This generates the CSS 👇
{"color": "red","@media(min-width: 420px)": {"color": "green"},"@media(min-width: 920px)": {"color": "blue"}}
Awesome!! 🔥 The array of colors map to the array of media query breakpoints that we passed into facepaint
.
But what if we want to use facepaint
with styled components? 🧐
Just use template strings!
const Block = styled.div`${mq({ color: ['red', 'green', 'blue'] })}`;
Now we can use our <Block>
styled component with baked-in media queries and we don't have to redefine
@media(min-width: 420px)
all over our app. 💥 This is a massive win.
And we don't need to specify each breakpoint. We could have just used mq({ color: ['red', 'green'] })
to only cover
the first breakpoint.
If you want to skip that first breakpoint and only cover the second one, you can use mq({ color: ['red', null, 'green'] })
because boolean
, undefined
, and null
values are ignored.
A lot of the time, we need to have conditional styling based on component
props. Based on the isReversed
prop, we can reverse the array
of colors, or the values at each breakpoint that we previously defined.
const Block = styled.div((props) => {const { isReversed } = props;const colors = ['red', 'green', 'blue'];return mq({backgroundColor: isReversed ? colors.reverse() : colors,width: ['100%', '70%', '50%'],height: '10vh',margin: 'auto',transition: 'all 0.3s ease',});});<Block isReversed={true} />;
You can check out this codesandbox for yourself 👍
Defining breakpoints and reusing media queries across apps is a lot easier with emotion
and facepaint
. Our apps will
be easier to maintain and we can be consistent in how we support viewports of all sizes.