Structuring our Styled Components | Part II

Building Composable Components

Alan B Smith
3 min readOct 5, 2018
Block Animation

Introduction

In a previous post, we discussed how our team organizes lower-level tiers of our UI by using Blocks, Elements, and Modifiers. This section will focus on Components, and Part III we’ll cover Compositions. But before we hop in, let’s review the overall structure:

  • Compositions: responsible for handling application-level data and logic
  • Components: responsible for handling UI logic and rendering Blocks & Elements
  • Blocks: responsible for rendering their child Elements and their own context (logic-less)
  • Elements: responsible for rendering themselves and handling their modifiers (logic-less)
  • Modifiers: responsible for modifying Block & Element styles in a predictable manner

Components

Rigid Component APIs

As mentioned above, Components are responsible for UI logic and rendering Blocks and Elements, and they look like common React components. Here’s an example:

Initial Notifier Component

Above is a Notifier Component. It’s responsible for rendering the notification‘s text and icon. The only logic it needs to manage is mapping the type of message to a particular icon name. This example has a rigid API and markup structure. Let’s talk a bit about Component API considerations.

Component API structure can range from rigid to flexible, and both have benefits. Rigid Components require users to know less about the underlying markup structure. For example, our current Notifier Component requires no knowledge of the underlying markup for its use:

Initial Notifier Component Example Use

If Notifier doesn’t have a lot of internal variation, this type of API may be the correct choice. This Component might have various states and messages, but it will always render an icon on the left and text on the right. By making this API more rigid, we’re reducing the mental overhead for composing this component. Users don’t need to remember which icon name and color modifier map to each particular type of notification, nor do they need to know about the markup structure underneath. All of those implementation details are hidden under the hood.

Reducing mental overhead improves Component consistency by making the right choices simple and removing opportunities for error. But the rigidity of this pattern can come at a cost as the component changes and evolves.

For example, let’s say we want some instances of the Notifier Component to not have an icon. Or perhaps, we want some to render icons on the right instead of the left. Our current structure isn’t flexible enough to easily allow for that. This API leaves us with only a few options. We could:

  • Add an additional prop and logic to hide or show icons
  • Update all instances of this component with a breaking change
  • Create a separate, very similar component

All of these options have their own pros and cons, but none of them feel like a great choice overall. Perhaps we could have structured Notifier’s API to be more composable and found a more flexible option. Let’s try that out.

Note: The composable component API patterns used below are discussed in more detail in Kent C. DoddsAdvanced React Component Patterns Tutorial and in Brent Jackson’s article here, both of which I would highly recommend.

We’ll use the React 16 Context API to pass our type prop from our Notifier Component to our Icon Element via a Provider. I’ll provide code snippets below, but if you’d like to see a working example, you can find a sandbox here.

components/Notifier/index.js

Composable Notifier Component

components/Notifier/Icon.js

Composable Notifier Icon Component

Now we can use our Notifier Component like this:

Composable Notifier Component Pattern Example Use

This version of Notifier is a lot more flexible. Users can easily add an icon or position it left or right of the text. A little more knowledge of the underlying markup structure is required, but a lot of the details are still hidden. In general, this composable pattern has been the most durable for our team. It’s flexible enough to be reused in many different contexts, but it also hides a lot of the complexity of the Component’s details.

Wrapping Up

Thanks for reading! Using composable patterns for our components has been great for allowing flexibility of use and also ensuring UI consistency. I hope the ideas here are helpful for you and your team as well! If you enjoyed this post, you should check out our other articles!

--

--

Alan B Smith
Alan B Smith

Responses (5)