Structuring our Styled Components | Part I
How we organize our applications at Decisiv
INTRODUCTION
Some Context
Our frontend UI team has put a lot of thought into how we structure our React applications, and after several iterations we’ve discovered patterns that work well for us. We’d like to share what we’ve learned in the process and hear more about how you’re organizing your applications as well.
This article is part one of a series, and focuses on Blocks, Elements, and Modifiers, and how we’ve adapted those patterns to deliver a predictable and consistent UI. We’ve also developed a pattern for interacting with application state and data, which we call Compositions. But we’ll discuss that topic in a follow-up post.
Our Inspiration
We really ❤️ styled-components. They’ve helped us create predictable, composable styles that we can reuse throughout our application. Everything you’ll read below is in that context. We’re also borrowing ideas from BEM for our structure.
Our Challenges
As our team and codebase grew, it became increasingly more difficult to ensure styles were applied consistently across the application. Our padding and margins were askew. We were approaching 15 shades in our grayscale, though the design spec only called for seven. We were creating duplicated components because preexisting elements were lost in the folder structure. All of this led to a lot of duplicated work and inconsistent styling. It was in this turmoil that we developed a better structure.
Our Goals
We wanted to create patterns that were easy to follow and prevented us from making mistakes. We wanted consistency baked in. We needed a way to describe how particular bits of UI interacted with one another and a clear delineation of responsibilities between them. Many of these details were hashed out over meetings, and most of them won’t make it into this article. However, the Block, Element, Modifier structure is really at the heart of the solution. I hope what follows is also helpful for you and your team.
BLOCKS
Overview
Blocks are the highest level of abstraction in the Blocks, Elements, and Modifiers concept. They are responsible for providing the context for Elements, rendering Elements, and handling UI logic. They are not connected to application state, nor do they handle any business logic. A Card
would be a good example.
This Card
component is a Block. It’s not responsible for any business logic, and it has child Elements: Header
, Image
, Text
, and Title
.
Card Block File Structure
NOTE: Blocks should not be nested. The folder structure should be flat. The goal is to encourage reuse of generic Elements.
Card Block Component
This pattern allows us to use these blocks like this:
Our Block structure provides BEM naming convention and makes the markup really readable. Another benefit is abbreviating what would have been a lengthy import statement. For example, these import statements:
Becomes this single import statement.
Each of the child Elements is available via the Block, and it reduces a lot of friction for our development team.
ELEMENTS
Overview
Elements are the smallest, indivisible parts of our UI. They are not responsible for application logic or UI logic, but they do handle their own Modifiers, which modify the Element’s style. While they generally exist within the context of a Block, they are not exclusively bound to them. At times an Element will stand alone. An example of a stand-alone Element would be an A
, Link
, H3
, or P
. These are universally applicable throughout our application and are not bound to a Block’s context.
Example File Structure
In situations where existing generic Elements will not function for your use-case, consider:
- Creating a Modifier for your specific style
- Restructuring the existing Element to accommodate your use-case
- Creating a new generic Element
- Or (as a last resort) creating a new Block-specific Element in that Block’s directory
The order of these options is intentional. As a general rule, try to use pre-existing Elements, modify and restructure as needed, and, as a last resort, create a new Block-specific Element. This helps us keep the UI more consistent.
An Example Element
MODIFIERS
Overview
Modifiers are small functions that allow us to alter the CSS properties of Elements. They live in the Element file and are solely responsible for modifying styles. They are not concerned with UI logic. We have an internal lib, styled-components-modifiers
, to help us predictably modify component styles.
NOTE: We recently open-sourced
styled-components-modifiers
. 🎉 You can find the library here. Give it a try, and let us know what you think!
An Example Modifier
Using Modifiers
To use our Modifiers, we pass an array of modifier names we want to apply as a prop.
From there, we find the matching modifier keys in our modifier config, concatenate the styles, and join them all together as a single string. styled-components
handles the rest for us.
Most of our modifiers only do one thing. But sometimes a single Modifier will apply multiple styles. For example if we need a button to be “disabled” or “active.” But we found that bundling a lot of styles together in a single Modifier limited its ability to be reused. It’s been better for us to have to declare more modifiers when we call the component than have weird side-effects and one-off modifiers.
WRAPPING UP
Our BEM component structure has been a really great pattern for our team. Blocks and Elements encourage reusable and flexible components. And modifiers keep our style alterations predictable.
Thanks for reading! If you enjoyed this article, you should subscribe to my newsletter! https://tinyletter.com/alanbsmith
Stay tuned for Part II!