Should You Use A ClassName Prop?
August 08, 2022 - 6 minutes
As React developers, everything is about components. The essence of React development is creating isolated, reusable, and composable components. When making decisions regarding the props that we’re exposing through those components, we’re designing the API. Whether it should be exposed to it, whether it should be an enum or boolean prop, whether it should be a separate component with its API, all of it falls under it.
And one of the most interesting and common API design challenges revolves around styling. It’s common to want to control the styling of a component as a user of the component. Whether it’s to implement variants, edge cases or make small tweaks. Having a certain degree of control over the styling aspects of a component is quite normal.
However, there are different methods to implement this particular type of API. Should we just expose a className
prop and call it a day? Or should the variants be implemented in the component and controllable via a static prop? Or should you combine them into a more composable structure? All of them are valid options, so what are the differences and how do you decide?
This article dives into that question by analysing all of them. We’ll look into their use cases, how to implement them, their advantages, and their drawbacks. By putting them against one another, we’ll look at the differences and explore which option makes more sense for which use cases.
Exposing The className
Prop
The most straightforward approach to implementing styling for a component is by exposing a className
prop that users can use to pass in a CSS class name to the component. Then, the component would then be responsible for setting that particular class name onto the appropriate HTML element.
const Component = () => (
<SomeOtherComponent className="outer-container">{/*...*/}</SomeOtherComponent>
);
This is probably the most common way of solving this issue and yields several advantages:
- It’s straight to the point. Developers know what the component expects from them and that makes it easier for them to provide the proper thing.
- It allows for the most control by users of the component. Since we’re passing a CSS class name to the component, it’s possible to adjust any add, overwrite, or adjust CSS rules. This provides the highest degree of control over the styling of a component.
However, despite it being such a common practice, there are also clear drawbacks to doing it in this manner.
- It’s easier to introduce inconsistencies in your UI. Nowadays, it’s not uncommon for products or projects to have a design system. One of the core philosophies of a design system is consistency. However, by exposing a single
className
prop and allowing complete control of the component’s styling, we’re making it easy to introduce a one-of exception or minor differences. - Another difficulty arises when there are multiple elements in a component that we want to control. Would it be wise to implement multiple
className
props, e.g. with different prefixes? From an API design perspective, that would feel very redundant. From a design system perspective, that would only amplify the previous drawback.
Based on all of these factors, we can take a look at the use cases in which it would make sense to implement a className
prop. In general, using a className
prop would make the most sense in scenarios where the locations at which custom styling has to be applied are limited, where small differentiations aren’t bad or interfere with the design system, and adjustments don’t fall into the existing styling implementations.
Add Individual Prop Per Styling Feature
Another option to implement styling for a component is to make it a more individually focused approach, namely adding individual props per styling feature. The component exposes a prop that directly controls a particular styling feature. As an example, think of a custom Button
component with a size
prop that takes either small
, medium
, or large
. Instead of passing a CSS class and worrying about all the possible CSS rules, the user can (dynamically) control it using JavaScript. Compared to the previous approach, there are some clear-cut advantages:
const Component = () => (
<SomeOtherComponent size="medium" variant="primary" innerVariant="secondary">
{/*...*/}
</SomeOtherComponent>
);
- The UI will become drastically more consistent. Since the component chooses which styling features to expose, the number of variations is controlled and limited. Unless the exposed props are designed suboptimally, it’s impossible to end up with edge cases.
- It’s made explicitly clear what will be changed when using the prop. A
className
prop doesn’t tell users what changes and what it’s meant for, it just inverts the control of that particular part to the user. On the other hand, having a prop per styling feature means an explicit name is attached to it. This makes it clear what users are changing and also what the different options are. - Users of the component don’t have to worry about how to implement the feature. Since the feature is implemented by the component itself, users of the component only need to decide whether they want to use it and how they want to use it.
Compared to the className
prop, this is the opposite way of implementing styling features for a component. While it emphasises aspects like consistency, explicitness, and abstraction of implementation details, it has to sacrifice different aspects.
- The number of props that the component exposes scales linearly with the number of styling features. For each new one that we want to implement, we have to add another prop on top of the existing API. In certain scenarios, it might even require multiple props to be added. Ultimately, this will make the component extremely difficult to use and maintain.
- The degree of control for users of the component is extremely limited. Since now the component chooses what aspects users get to control, users are limited in what they can do with the component. While for consistency’s sake this is a benefit, it’s a drawback for control’s sake.
Once again, let’s take a look at the use cases in which it would make sense to implement it this way. In general, using exposing styling features through individual props would make the most sense in scenarios where the number of options for changes is minimal. If the variations of a component are always known to be in a certain set of deterministic possibilities, then the number of props can also be kept limited
the locations at which custom styling has to be applied are limited, where small differentiations aren’t bad or interfere with the design system, and adjustments don’t fall into the existing styling implementations.
Composable Structure
The previous two implementations are opposite of each other on the spectrum of degree of control and consistency. However, in certain scenarios, it’s also applicable to use a solution that is in between these implementations.
That solution is not necessarily something new, but rather utilises both the previous implementations into a composable structure. This results in more layers in your components, which means that individual props are easier to apply to the appropriate layers and makes it easier to maintain components.
We’re creating more components to avoid overloading one component with all the styling props and spreading them out over several components that are meant to be used together. This yields most of the benefits from both approaches, while only inheriting a few drawbacks.
const Component = () => (
<SomeOtherComponent.Outer size="medium" variant="primary">
<SomeOtherComponent.Inner variant="secondary">
{/*...*/}
</SomeOtherComponent.Inner>
</SomeOtherComponent.Outer>
);
Props are exposed by the appropriate components, which make them scoped, more explicit, and easier to understand. However, it does mean we’re creating more components, which can make maintenance more difficult in the long run. The biggest drawback of this approach is that it only works in scenarios where exposed styling features apply to DOM elements at different levels. Because if that’s not the case, it’s technically not possible to separate the initial component into multiple, composable components.
Final Thoughts
No front-end application would look good without CSS. As such, styling plays an integral role in the development process of React components. In particular, one of the most interesting and common challenges in React development is designing a component’s API around styling features. One of the most common ways of doing so is exposing a className
prop and letting the user do their thing. But is that a good idea?
This article dove into that question by analysing this approach and several other ways of implementing styling features for components. We looked into the implementation, its advantages, drawbacks, and use cases. By putting them against one another, you now have a clear idea of when to which implementation and how to create your own.