Tailwind- or Component-style Styles?
A bad design system is worse than none, and the how behind your application’s style plays a big part in that designation. The system needs building, third-party libraries need picking, the platform’s long-term maintenance needs planning, and there’s no perfect path to choose.
As this article’s title suggests, let’s dive deeper into two styling paradigms. Spoiler: They’re both perfectly viable options, but their strengths are suited for different projects.
The History of CSS
CSS is old enough to rent a car — and the web has changed quite a bit since ‘96 — so the unabridged history is outside the scope of this piece. For our purposes, we’ll focus on the sharp uptick in application size and complexity over the last decade or so.
On its own, CSS doesn’t really mandate how you use it. You can write code that’s super specific or super broad, co-located or meaningfully split, and organized (or not) by whatever arbitrary means you like, all at the same time.
Tending a small, open-ended garden (stylesheets) is easy for one gardener, but problematic as the greenhouse and team grows. What should be planted, and where? How do we care for mature plants? Is someone checking with the kitchen to see if we still need tomatoes? Leadership and policies help, but both are still fallible when you add stress or change.
We started with methodologies like BEM, OOCSS, and SMACSS, and paired them with tools like Sass and Less. They added some structure to an otherwise lawless process, but still relied on the honor system, code reviews, and practiced know-how.
Christopher Chedeau’s 2014 talk on the (then-controversial) idea of CSS-in-JS was an inflection point. At Facebook scale, the quirks of CSS couldn’t stay unchecked, and the honor system needed automation and enforcement. Those concepts — and the leaps in theory that followed — led to the modern paradigms and libraries that we’ll cover from here on out.
The Highs and Lows of Component Styles
First up: component styles. Here, we’re organizing the look and feel into discrete, reusable pieces, similar to object-oriented programming. If our application is a LEGO kit, a 1×3 blue angled brick is a component, and we can drop one in wherever necessary.
Here’s an example with some plain CSS:
What Component Styles Do Well
- Limit scope. Components are self-contained, so we’re able to skip cascading side effects whenever they’re added, changed, or removed. If the application doesn’t use or need 1×3 blue angled bricks anymore, it’s safe to delete them.
- Map cleanly to design. As a mental model, we’re used to the connection between an interface design and its major components. Buttons look like this, forms look like that, and those characteristics directly translate over to code.
- Manage complexity. In a component’s self-contained world, we can cleanly co-locate whatever complexity needs handling. Buttons, for instance, may need states like active, disabled, focused, loading, and so on.
Where Component Styles Struggle
- They’re tough to organize. If a table row has an avatar image in it, is that avatar part of the table component? Or is it its own avatar component? It’s difficult to pick a chain of ownership, and the choice may change as a project progresses. Further, nesting components inside of other components can threaten their self-contained traits.
- Options are tough, too. Back to our LEGO example, what if we want that 1×3 blue angled piece in multiple colors? Is that a variation of the component, or do we need a completely separate “dye” component to determine color?
- They’re tough to name. Naming is one of the harder problems in programming, and you’ll have to give one to each of your components. Is this a tooltip, popover, dialog, or modal? How about an alert, notification, or toast?
- Where do one-off tweaks go? Maybe there’s a compelling design reason to give just one instance a bit of extra margin on the right. Is that a component-level option? Is that handled by a new component? How do we avoid overengineering a solution for a very small tweak?
- They lean on deeper CSS knowledge. In a like-for-like comparison — and given the struggles we’ve listed so far — components deal with CSS and how to abstract it more directly than functional styles.
Which Libraries Tackle Component Styles?
New tools pop up all the time, but here are a few places to start:
Diving Into Functional / Atomic / Tailwind Styles
If component styling is analogous to object-oriented programming, functional styling is akin to functional programming. It has a slight branding problem, though — ”utility-first” and “Atomic CSS” (not to be confused with atomic design) are popular variations on the name. Further muddying the water is the popularity of its prized framework Tailwind CSS, creating a slight Kleenex versus tissue brand association situation. We’ll just say functional.
Naming aside, functional styling is composable. Here, we’re not defining a 1×3 LEGO piece that’s angled and blue. We’re predetermining what attributes a piece is allowed to have (1×3, angled, blue), then applying those characteristics as needed. It’s a subtle and important flip.
Here’s an example of composed styles:
What Functional Styles Do Well
- Abstract CSS away. CSS seems simple, but it’s a tough language to master and stay on top of — especially if you’re trying to keep up with everything else in the industry, too. Instead of worrying about the correct way to make something blue, you’re given an abstracted instruction that does it for you.
- Let patterns emerge. Components force some guesses: You have to define the pieces to start using ‘em. Functional styles let patterns emerge organically, giving developers the chance to optimize based on actual usage.
- Handle one-offs and uniqueness. Pieces of the application are composed from a series of preset instructions, making one-offs and unique combinations just as easy as everything else. You’ll always know why something looks the way it does, and have a direct path to change it.
Where Functional Styles Struggle
- Some characteristics shouldn’t mix. The system may allow you to make text large and blue at the same time, but that doesn’t mean the designers want it to happen. Like peanut butter on an orange, some potential combinations just don’t mix.
- Some patterns will look suspiciously component-like. Once you compose a button, that series of instructions will probably be needed on all buttons. Functional repetition looks a lot like a verbose component.
- You’ll have to pick up a new shorthand. Names like ‘space-y-4’, ‘pt-6’, and ‘dark:text-slate-500’ are what you’ll need to apply these sorts of styles. It’ll feel natural, eventually. In the meantime, it’ll be tough to know what an element is responsible for at a glance — like trying to guess a meal from a list of ingredients.
- Changes can be labor-intensive. If ‘text-slate-500’ should actually be ‘text-slate-600’ (which would make text a slightly darker shade of a slate blue), you’ll need to find and replace all of ‘em throughout the whole codebase. Caveat: If your markup is already component-ized with React, Remix, or virtually any other modern framework, this generally isn’t a concern.
- Complex, interactive pieces can be overwhelming. Functional styles (typically) are applied directly to an element. If that element needs a lot of instructions across a number of screen sizes and states, the resulting code may be tough to look at.
Which Libraries Tackle Functional Styles?
There’s a clear leader, but Tailwind isn’t the only functional option:
Picking a Styling Strategy
So, any of the paradigms, frameworks, and libraries we’ve mentioned so far could do the job just fine. We’ve spent a disproportionate amount of time on tradeoffs and drawbacks, but there’s a long list of things component and functional styles both do well, too: theming, type checking, managing reuse, packaging for production, pairing with other libraries and plugins, etc.
When it comes down to it, here are a few considerations that may tip the scales and help you pick a path.
Independent Factors
Ideally you’ll grab the methodology that best suits the situation, but some factors make the decision for you:
- Your team is already versed in one. Could they pick up the other? Sure, but using either styling approach at scale goes best with some experience.
- You need a library that relies on one. If you’re looking at a UI collection like shadcn/ui, for instance, it’d make sense to also use Tailwind.
- The codebase already uses one. This may be a no-brainer, but it’s better to pick a lane and keep a project consistent across the board.
When to Pick Component Styles
- Complicated layouts and animations. Complex state management and animation in CSS stretch functional styles. You’ll typically turn to something component-like, even if everything else is utility-first.
- Dramatic responsiveness. Similar to the first point, if elements on the page change dramatically across device sizes, it’s easier to manage that set of properties in a component-style context.
When to Pick Functional Styles
- Engineer-heavy maintenance. Keeping up with CSS, proper ways to abstract components, and naming is a tall ask for developers with plenty of other priorities. If long-term maintenance is mostly handled by engineers, the utility-style styles keep them on track.
- Prototyping. For quick tests and demos, functional styling is speedy. Developers get to skip naming, abstraction, and file switching when tackling (temporary) look ‘n’ feel.
Viable, Opposing Styling Paradigms
Self-contained pieces. Composed, emergent instructions. CSS has progressed to the point where there are two very different — but very viable — options for styling applications.
Opinions vary, and it’s a good idea for your front-end team to pick both up. The right choice is a project-to-project decision.
Designing Links: Tips for Implementing a Fundamental Piece of the Web
Because links are one of the primary interactions we have online, website link design is of great importance. Read our comprehensive guide now.