For a recent project, I was working with a <Button>
component that had two different variants: "default" and "minimal".
Using React and Tailwind CSS, this component is very easy to implement:
components/Button.jsx
function Button({ variant = 'default', children, ...rest }) {return (<buttontype="button"className={twMerge(variant === 'default' && 'bg-blue-500 text-white p-4',variant === 'minimal' && 'bg-transparent text-blue-500 p-0')}{...rest}>{children}</button>)}
However, now my client designed a section where the <Button>
component needed to
appear in different variants depending on the screen size.
Rendering Different Variants for Different Screen Sizes
If the React application was rendered entirely on the client side, this would be simple:
JSX
const windowSize = useWindowSize()const isMobile = windowSize.width < 640return <Button variant={isMobile ? 'default' : 'minimal'}>Click me</Button>
But of course: The React app was server-rendered. Ugh!
The above approach would still work but it would cause content flashes and layout shifts as soon as the client code takes over.
Here's the solution I came up with. It's kind of ugly but it works.
Using SASS to Support Different Variants for Different Breakpoints with SSR
First, I created SASS mixins for the two button styles:
SCSS
@mixin button--button {@apply bg-blue-500 text-white p-4;}@mixin button--minimal {@apply bg-transparent text-blue-500 p-0;}
Then, I added classes for each possible breakpoint / variant combination:
SCSS
// Mobile.button--default {@include button--default;}.button--minimal {@include button--minimal;}// Tablet+.button--md--minimal {@media (min-width: theme('screens.md')) {@include button--minimal;}}.button--md--default {@media (min-width: theme('screens.md')) {@include button--default;}}
And as a final step, I let my React <Button>
component accept a separate variant for each breakpoint:
JSX
function Button({ variant = 'default', mdVariant, children, ...rest }) {return (<buttontype="button"className={twMerge(variant === 'default' && 'button--default',variant === 'minimal' && 'button--minimal',mdVariant === 'default' && 'button--md--default',mdVariant === 'minimal' && 'button--md--minimal')}{...rest}>{children}</button>)}
In the actual code base, the button also had different icons that would be shown depending on the variant. With this SASS-supported approach, it was pretty easy to add that functionality as well.
The above approach also works with CSS modules which is great if your code base already uses them.
Conclusion
I love the prop-based approach for styling React components but it starts breaking down in a server-rendered environment where we can't access the user's screen size via JavaScript.
In these cases, we need to bridge the gap using more traditional CSS techniques.
These kinds of experiences have also taught me to avoid server-side rendering when it's not necessary.
I hope this articles helped you out a bit. If you have a better solution to the problem I'm describing, please let me know on Twitter. I'd love to hear from you!