DRY-ing up styled-components
Publikováno: 23.11.2020
I like working with styled-components. They allow you write CSS in your JavaScript, keeping your CSS in very close proximity to your JavaScript for a single component. As a front-end developer who loves to dissect a web page and break it down into reusable components, the idea of styled-components brings me joy. The approach is clean and modular and I don’t have to go digging in some gigantic CSS file to see if a class I need already exists. … Read article “DRY-ing up styled-components”
The post DRY-ing up styled-components appeared first on CSS-Tricks.
You can support CSS-Tricks by being an MVP Supporter.
I like working with styled-components. They allow you write CSS in your JavaScript, keeping your CSS in very close proximity to your JavaScript for a single component. As a front-end developer who loves to dissect a web page and break it down into reusable components, the idea of styled-components brings me joy. The approach is clean and modular and I don’t have to go digging in some gigantic CSS file to see if a class I need already exists. I also don’t have to add a class to that never-ending CSS file and feel guilty for making that already gigantic file even bigger.
However, as I continue down the path of using styled-components, I have started to realize that, while it is great to separate out CSS styles to be specific to singular components, I am starting to repeat myself a lot for the sake of organizing my styles on a per component basis. I’ve been creating new CSS declarations, and thus, new styled-components, for every component, and am seeing a great deal of duplication in my CSS. No, the styled-components aren’t always exactly the same, but two of the three lines of CSS would match two of the three lines of CSS in another component. Do I really need to repeat this code every place I need these styles?
Take flexbox, for example. Flexbox is great for responsive layouts. It will align items a certain way, and with minimal changes, can be tweaked to look good across different screen sizes. So, more often than not, I find myself writing:
display: flex;
flex-direction: row; /* the default; in react native, column is the default */
Almost as often, I found myself writing:
display: flex;
flex-direction: column;
The two code snippets above are fairly common: the first takes all of the child elements and positions them next to each other — from left to right — in a row. The second takes all of the child elements and positions them above and below each other — from top to bottom -— in a column. Each of these code snippets can be made more specific; however, we can add different properties to further specify how we want the child elements laid out on the page. If we want the elements spaced evenly across the available screen width, for example, we can add the following line to each code snippet:
justify-content: space-evenly;
Additionally, there are other properties, like align-items
that we can add to further customize the layout of these elements. So, if we have three different components that all require a flexbox layout, but have additional differing properties, how can we use styled-components in a non-repetitive way?
Initially, it makes sense to create three sets of styles for each component, like this:
// component one
const ComponentOne = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-start;
`
// component two
const ComponentTwo = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
`
// component three
const ComponentThree = styled.div`
display: flex;
flex-direction: row;
justify-content: space-evenly;
`
The styles listed above will do the job. All three components have child elements laid out in a row — positioned from left to right — with different spacing between each child element. However, having the same two lines of code repeated three times adds CSS bloat.
To avoid repeating ourselves, we can extend a base component to each of the other components and then add the additional styes we need to those components:
// flex row component
const ExampleFlex = `
display: flex;
flex-direction: row;
`
// component one
const ComponentOne = styled(ExampleFlex)`
justify-content: flex-start;
`
// component two
const ComponentTwo = styled(ExampleFlex)`
justify-content: space-between;
`
// component three
const ComponentThree = styled(ExampleFlex)`
justify-content: space-evenly;
`
That feels much better. Not only does this version — where we extend off of the <ExampleFlex />
component — remove repetitive code, but it also keeps the code related to displaying items in a row in one spot. If we needed to update that code related to the direction of the items to a column, we can do that in one spot instead of three.
Important note: When you’re extending styles off of another component, the styles that inherit from that base component need to be listed after the base component like in the example above. Placing <ComponentOne />
above <ExampleFlex />
would cause an error that reads: Uncaught ReferenceError: Cannot access ‘ExampleFlex’ before initialization.
To take this idea one step further, the following demo shows a situation where you might need similar styles on two different UI elements on the page, but those elements each have their slight differences.
As you can see, both the navigation that lives at the top of the page and the footer that lives at the bottom of the page need to be laid out in a row direction on larger screens and then shift to a column layout on mobile devices. The two elements differ, however, in that the navigation at the top of the page needs to be aligned to the left to leave room for the logo on the right while the footer links need to be aligned right. Because of these differences, it makes sense to create two different styled components for each of these elements; the <Navigation />
element for the top navigation and the <Footer />
component for the bottom of the page navigation.
The top navigation styles look like this:
const Navigation = styled.div`
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 10px;
width: calc(100% - 20px);
@media (max-width: 768px) {
flex-direction: column;
}
`
While the bottom footer styles look like this:
const Footer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-end;
padding: 10px;
width: calc(100% - 20px);
@media (max-width: 768px) {
flex-direction: column;
}
`
The only difference in those two elements? The justify-content
property. Additionally, the <LeftSideNav />
component uses display: flex
with the same media query.
Outside of these three components sharing a lot of the same CSS, the <NavItem />
component and <FooterNavItem />
are very similar link components with slight differences. So how do we DRY this up?
Looking at the example below you’ll see that CSS that is reused across multiple components can be pulled out into its own component that we extend off of to make the specific changes we might need for more specific components.
With the changes put in place, our JavaScript file that holds all of the styled-components is 10 lines shorter than the previous version. While that may seem like a small difference, as applications grow, these changes could help minimize the CSS that is shipped with an application as styles continue to be added.
There’s also the “as” polymorphic prop!
In addition to extending styles from one component into another, styled-components also gives us a polymorphic prop called “as” that applies styles from a given component to another element as long as that new element is specified. This is helpful in situations where the user interface for two elements might look the same, but the underlying HTML functionality behind those two elements is different.
Take buttons and links styled as buttons as an example. While they seem to have similar functionality at first glance — they both can be clicked to trigger an action — the two serve functionally different purposes. A button is great for submitting a form or changing the layout of the the current page you’re on, but a link will take you to a resource.
I’ve created a Pen below that illustrates this. At first glance, the two buttons look the same. However, the one on the right, is actually an <a>
element styled like a button, while the one on the left is an actual <button>
element. These two buttons could be part of a site navigation down the line, with one needing to link to an external site. At a first attempt in building out these two elements, it makes sense to see code for each component like in the first example we looked at.
If we know that these two elements will have the exact same styles, we can style each one as a <Button />
, grouping the styled-component with the JavaScript that goes along for the button we are creating. Then we apply those same exact styles to a <Link />
component that we specify:
Looking at the pen above, you can see that we have removed all duplicate CSS related to the styling of these two elements. Rather than repeat the CSS for the button to use on the link component, we can apply pass in a value to the as prop like so:
<Button as="a" href="#" ... >I am a Link!</Button>
This gives us the ability to change the element to an HTML tag while keeping the styles that we have already defined for the Button.
Extending baseline styles — and perhaps even keeping some them together in a single globalStyles.js
file — is an effective way to DRY our styled-components code, making it much more manageable. Keeping CSS to a minimum not only enhances the performance of a website, but it can also save developers from having to dig through lines of CSS in the future.
The post DRY-ing up styled-components appeared first on CSS-Tricks.
You can support CSS-Tricks by being an MVP Supporter.