Diving into Component and Atomic Styling Methodologies
Styling applications at scale is a classic web development problem. Every honest examination of CSS implementation seems to revolve around the issues of scope, consistency, and optimization. A history of approaches to these challenges has now evolved into two distinct branches of styling: component and atomic.
Component-based CSS approaches an application as a unified set of otherwise isolated parts. You can create these components and continually modify them without affecting each other.
Atomic CSS provides a large pool of possible styles that each UI element can take on. Careful configuration of this pool means that the usage of color, spacing, font choices, etc. are limited to what is intended. There are ways to organize these styles into reusable patterns, but this is not enforced by frameworks. (Note that this shouldn’t be confused with the concept of atomic design).
With atomic frameworks seeing a rise in usage, it’s worth comparing the new kid on the block with the more traditional component methodology. Both are imperfect, valid approaches and can shine or fall short depending on the scenario. Let’s dive in to explore the differences and find which situations each is best suited for.
The component strategy: limit scope
The philosophy of component styling centers around scope. It contends that the global, cascading nature of CSS is dangerous, so you must add structure to limit a style’s scope as much as possible. This is accomplished by building a system of reusable CSS components.
Just as a web application is broken into units of code, you can separate CSS into units of style. Each component has its own properties and doesn’t share classes with other components. This self-contained design means that one can reliably style and re-style a component without affecting other parts of a website.
This view is materialized through rules around how to structure and name classes, such as the BEM or ITCSS architectures. Developers can optionally make use of various frameworks (such as Bootstrap, Bulma, and Materialize) to formalize requirements for writing or applying CSS.
You can nest components inside each other and have subcomponents of their own. Nesting can cause some styles to bleed through, so take special care to keep them contained. Below is an example of a simple card design implemented using BEM-like classes.
Notice that the complexity in defining styles lies in the stylesheet, and the markup is dedicated primarily to the structure of elements. This is a key departure from atomic CSS, where the complexity of style assignment is in the markup.
Why do people love using component methodology?
We already think of websites as collections of buttons, headings, inputs, and all sorts of UI elements. Organizing CSS classes as components is a natural extension of that concept. And with the design and dev community’s growing focus on building design systems, the path to writing styles as components is clear cut.
The methodology’s emphasis on scope means that determining where in the UI to draw the boundaries of components is as much a part of the process as defining properties. This is easier said than done, as the component’s scope is not always immediately clear.
Take the heading inside the card above, for example. Is the text style used unique to the card and therefore should be a card subcomponent? Or is the text style something that will be used in other contexts, and thus exist as its own component?
Once you define a component’s boundaries, you are unlikely to step on the toes of other interface elements and are freed up to focus on styling that component.
The atomic way: relentless reusability
Atomic CSS hones in on the idea that much of web interface boils down to the same properties. Instead of defining the same properties across many component classes, what if a single class could be reused wherever it was needed?
This line of thinking leads to the heart of atomic CSS: single-purpose classes that are strung together to style markup. For instance, we can use a class that sets the text color, another class for a certain font size, and another to add padding around an element.
We don’t need to define a “button” class — we can just use a combination of classes that come from our pool of pre-determined properties.
These atomic classes are typically generated by a framework, such as Tailwind, Tachyons, or ACSS. A configuration file lets you assign the values each property can have, so classes can be generated for each respective style. Color scales, font sizes, spacing options — every property you want to customize — is set in this file.
In effect, this configuration file whitelists which properties can be applied. Without changing the config (or creating classes outside the atomic setup), developers are constrained to only using approved styles. As we’ll discuss soon, there are pros and cons to this.
Below is the same card example previously shown, but implemented using the popular atomic framework Tailwind (with classes already configured). Notice the difference in how classnames are applied to the same structure and content (but result in the same visual outcome).
The glaring reality: both component and atomic CSS have gaps
At the end of the day, either way of using CSS can lead to the same result. Yet the approaches to conceptualizing and managing those results are quite opposite each other.
In fact, each methodology is missing the value that the other brings. The irony is that, for most applications, component and atomic approaches need to borrow concepts from each other. The tension lies in how much to borrow.
The need for utility classes in a component system
While the component approach has much to say about the structure and scope of its components, it remains mute on how to address certain other concerns. For example, how do you manage frequently repeated styles across isolated components? Enter “utility” classes.
While atomic CSS (also called utility-first CSS) allows classes to be reused anywhere, the whole point of components is isolation. But what if many components tend to require the same kind of tweaks in certain contexts? Do you recreate these same tweaks across many components? Or do you adopt these tweaks as single-purpose classes that can predictably override component styles?
Common examples include utility classes that apply the same animation, font weight, or spacing nudge wherever they’re placed. To address the scope and specificity issue, utility classes are set to trump component styles. This allows for overrides that relate with components in a predictable manner.
The need to abstract atomic patterns into components
Adam Wathan, creator of Tailwind, describes atomic CSS as preventing “premature abstraction”. Instead of defining components from the outset based on intuition, classes are added to markup as needed. Then, code can be abstracted as patterns emerge.
Atomic CSS leans on the application’s existing components/templates to reduce repetition. So if your application has a card component, the only place you’re likely to need to define your card classes is in that component.
If you are writing repeated class pairings for something that doesn’t have its own component/template — those classes’ properties can be consolidated to a single class. For example, in Tailwind, if you consistently represent a button as white text on a blue background with some padding on all sides, you could create a `button` class that applies all that same properties as `color-white`, `bg-blue-500`, and `p-2`.
This “button” class is effectively a component class. Yet it still works within the atomic concept, since it is a low-level class that can be reused. How well these new classes are integrated into the overall system is dependent on the atomic framework being used.
The problem is that this step of abstracting atomic classes is optional and cannot be enforced by the framework. When patterns are abstracted after-the-fact, design consistency is in danger.
How do you know if a style combination has already been used? Searching the codebase is near pointless as the many classes are used all over and can be arranged in any order.
How do you know if you’ve abstracted enough? Continually analyzing patterns inside markup gets tiring and takes time. It’s tempting to avoid altogether. Yet abstraction is key in the process for styles or interfaces that will evolve over time.
Now that we’ve seen that both component and atomic architectures are at their best when they borrow from each other, let’s continue our comparison.
The right approach for every project type
When to use component CSS
Rarely will either methodology be the perfect fit for a given application. Component-based styles are not without drawbacks, but the system has remained the go-to strategy for many teams. Use this approach:
- to implement a design system that’s already componentized.
- to utilize CSS-driven effects for a highly interactive site.
- to augment existing styles (unless those styles are atomic).
- to share components across multiple sites.
When to use atomic CSS
They may draw polarized opinions, but atomic frameworks continue to advance and prove the usefulness of this approach. Thanks to its composability and less opinionated concepts, an atomic framework can be an ideal candidate:
- to build prototypes quickly and efficiently.
- to extend styling responsibilities to more types of developers.
- to split the difference between fully custom and plug-and-play.
Sometimes the best tool for the job is the tool you know best. Sometimes it’s better to have a bigger toolkit. Regardless of how you tend to approach CSS, being familiar with the two major branches will provide a richer understanding of how to style web interfaces.
When you design a learning management system, you have to put the students’ experience first. Discover tips and strategies for getting the most out of your LMS.