Utility First Tailwind CSS with Sam Selikoff
In this interview, Sam Selikoff shares with Taylor Bell some of the whys and hows about Tailwind CSS and its impact on frontend web development.
Taylor's questions are bolded, and Sam's answers follow inline.
What do "Expressive" and "Semantic" mean when talking about CSS?
Classes are expressive in that they do exactly what they say they're doing. You don't have to go through a CSS file to see what's going on.
For example text-blue
instead of header-text
.
CSS is declarative in that you say "font color is blue" and you don't have to know or care how the browser actually makes that happen. Same with HTML— you just say <p>
and the browser does it.
On an episode of the EmberMap Podcast, Tailwind's creator Adam Wathan explained it as taking the semantic stuff and flipping it on its head.
There's always a coupling between HTML & CSS. The question is how tightly coupled are they? How hard is it to change? Traditionally you would write a CSS class like .mirage-page-header
then add it to your HTML. You write CSS that hooks into your HTML directly. In theory, you're writing the HTML for the Mirage page that on its own should have every tag where it is supposed to be, and could have any CSS style sheet hook into it and style it correctly. This is the idea behind CSS Zen Garden. We're taught to make our HTML semantic (which it should be, though for other reasons) but then you're taught to write specific CSS for the specific HTML document.
What Tailwind (and functional utility CSS frameworks in general) say is the opposite. Instead of writing generic independent HTML that is then hooked to specific CSS, you can get a lot further if you write generic CSS and hook into it from the HTML.
Even though CSS Zen Garden shows you how cool it is that you can style the same document in many radically different ways, at the end of the day when you look at any website the structure of the HTML is written in a specific way because they want the site to look a a specific way.
If you accept the fact that we are going to be altering our HTML to make it easy for us to style, with semantic structure where it matters, Tailwind gives you well thought out generic CSS you can apply to it.
You have to couple one way or another, and using Tailwind allows you to focus on highly specific HTML instead of highly specific CSS.
How do you explain functional CSS to somebody?
Single purpose utility CSS classes. Libraries like Tailwind or Tachyons, or even Bootstrap classes like text-center
. Some people draw parallels to functional programming, but I don't really worry about that. The approach of classes doing a single thing is the important part.
Is there a difference between "Functional CSS" and "Utility CSS"?
Some people might get technical and talk about immutability or purity which are notions from functional programming, but there's no practical difference.
With functional/utility CSS, the classes are general purpose and HTML hooks into them, instead of the traditional way which was the other way around.
You mentioned Bootstrap's text-center class. What's your take on the problem that Bootstrap was supposed to solve?
They were trying to get some general and good looking styles packaged up in a way that's easy for people to use, while taking care of cross-browser bugs. Design Systems were just starting to become a thing, and Bootstrap's was general enough it could work in back-office applications or frontends. This combined with an easy to use API helped it to really take off.
You've pointed out that Tailwind helps solve customization issues with Bootstrap. Before Tailwind came along, what would people do when it became time to customize?
Lots of people are familiar with having used Bootstrap for their app, and then new requirements come up that go beyond what Bootstrap gives you. Some developers came up with ways to customize it, and others would get frustrated enough to either abandon it on their current app or say "I'm not going to use it next time."
It felt like there was a huge gap between either buying-in to Bootstrap and getting a bunch of stuff but you might hit limits later vs. starting from scratch and building the world yourself. It was a frustrating spot to be in.
People started developing methodologies for building design systems and CSS frameworks from scratch– how to build, scale, and maintain them using BEM or Object Oriented CSS or others.
Some frameworks tried to do provide higher level things like Bootstrap did that were still customizable later. Bootstrap added in Sass variables to customize your brand, but that's not really how most people used it.
This is why the space was right for Tailwind to come in.
There's the common complaint of "It looks too much like a Bootstrap site"— Is there a chance of "It looks too Tailwind-y"? What's the balance?
There's more danger of this with Bootstrap since it includes a Navbar by default and you can tell it's the Bootstrap default right away.
Tailwind was built from the ground up to be a an engine for creating your own design system, but over the last couple years Adam and his team have baked more of their own design opinions in as defaults. Being lazy developers, we rely on them because they're fantastic defaults.
But you can definitely start to see similarities across sites, whether it's because they're relying on default colors or using Tailwind UI, which gives us something that's closer to what Bootstrap originally gave us albeit more customizable.
To the extent that people rely on defaults, there is that risk, but you can still find tons of sites that use Tailwind and look nothing like each other. The risk is there, but it's not as big of a deal.
One of the big complaints about utility CSS is that there are lots of classes that are hard to read. Was this your initial reaction?
When a former coworker introduced me to it, he showed me his template code then had me drive. He encouraged me to guess the classes as we went along. Once you learn a little bit about how the classes are, it starts to feel like magic since you never have to leave your HTML and can try things out and see what they do. It helps you build momentum fast.
You can use the "tinkering loop" of trying out the different classes and see how they look.
When you use Tailwind, do you typically go from scratch or work off of an existing design?
I usually do a high level design in Figma to get a general idea of what I want, then do the tinkering process of finding spacing values and text values to see what looks good.
I don't spend that much time on a design up front because things always come up in development and I'm fast enough with Tailwind that the design process becomes part of the build process.
Starting Figma or even a paper and pen sketch will ultimately save some time, but sometimes I do just dive in and start coding right in the browser from my head.
Do you have a design background?
I like to fiddle around it, but no background. I've designed stuff that I've worked on, and I like to work on getting better at it. I like being able to make something look good, even if I'm not working from a professionally designed mockup.
Do you think Tailwind has made you a better designer?
Absolutely. Not just Tailwind itself, but following Adam and Steve Schoger as well.
Their goal is to make you build better designed websites, and they way they're doing it is by sneaking in defaults into a library that makes building websites fun.
The things the library includes like different color scales and font weights and different types of shadows that you would see in professionally designed websites.
It's like a Trojan Horse to make developers better at design as you pick up more about Tailwind.
How much CSS do you need to know before you go Tailwind?
I think you're only going to be as good at Tailwind as you are at CSS. For example, Tailwind doesn't give you a layout grid the way Bootstrap does. Tailwind does have grid now, but only because CSS has grid.
Things that Bootstrap or other frameworks would abstract away, Tailwind doesn't because it's only utilities. If there are CSS rules that you don't know how to use, Tailwind might be able to help from its documentation but that would be you learning CSS in general, not Tailwind specific.
Tailwind is an abstraction for people who know CSS. I've sharpened my CSS skills by using Tailwind because of the selective classes and rules they've chosen and how they use them.
But ultimately, if I was teaching someone new and they didn't know CSS or web development, Tailwind would come after CSS.
What are the must-know parts of CSS someone should know before starting with Tailwind? Flexbox, because it's replaced so many of the ways we used to do layouts. I use it all the time, and part of the reason I use it all the time is because Adam uses it all the time.
I didn't realize how powerful it was until I became more familiar with Tailwind's rules and how they lay it out.
Other display properties like block and inline. Layout is a big part of it, colors.
Working with images– knowing how they fill or don't fill their space, how they respond.
Media queries for making things responsive.
Do you subscribe to the mobile-first philosophy?
Absolutely, I build everything mobile first. Sometimes I design mobile first, sometimes I design desktop-down.
Part of Ethan Marcotte's argument for going mobile-first is that it makes you focus on the essential things. If you don't have room to just fill up the screen with things that aren't important. It's also technically easier to implement on the small screen and add as things get bigger.
I have found that sometimes when you design mobile-first the desktop experience suffers, so depending on the project I may try to design separately. For example if you were building VSCode, you would optimize for the desktop experience even if you code it mobile first.
Tailwind nudges you toward mobile first, because all the media queries are above the breakpoints.
When you have your Figma design or pencil sketch, do you build the entire thing for mobile first, or do you break it up into "the top part" of mobile then switch to the larger sizes and work your way down?
I build the entire screen in iPhone size, then move up to iPad, then Desktop.
Most of the time I try to make things that can be adapted since it makes other developers' lives easier, and the user experience is better since the app is similar on all devices.
There are times when you want to swap out components completely when changing sizes. Trying to contort HTML made for one size into another by using wrapper divs or Flex tricks isn't worth it.
Tailwind has the @apply
directive that allows you to reuse classes. There's also framework-specific ways to make reusable components. Which approach do you typically use?
I use the component way, or even a Rails partial. Adam discourages people from using the @apply
directive. It's like another Trojan Horse where people might think that having several classes is messy so they want to abstract them away, but instead they end up in trouble when they need to extend it later.
Having utility classes means you can do something like center text without having to go edit a CSS file.
There's the idea of "Thinking in React," but is there a "Thinking in Tailwind"?
I don't think so. To me, "Thinking in Tailwind" means if you were approaching a CSS problem that you would do it differently than you would without the Tailwind mindset.
Instead, for example, it would be like getting better with Flex since it becomes easier to use. You don't really think differently about solving the problem.
The most dramatic thing that changes with Tailwind is that you aren't writing CSS.
The "Thinking in React" approach of one way data flow actually does fundamentally change the way you approach developement.
People write CSS differently, but Tailwind boils down to CSS. If someone is a Grid Ninja, they might use it in ways that I don't because I don't use it a lot.
Using Tailwind or not doesn't matter as much as using the parts of CSS you're familiar and comfortable with, along with what the Tailwind docs nudge you towards.
How do you approach dealing with duplication and DRY?
This is an interesting follow-up after the "Thinking in Tailwind" question, because one of the things that did change my opinion after using Tailwind is to not worry as much about duplication. You will quickly see as your requirements change how much easier it is to change the code you abstracted to early.
In the same way when you learn good OOP practice to keep things changeable. You can say "I've only duplicated this twice, let's wait a bit" to avoid a wrong abstraction that will hurt you.
Suppress your natural instinct to abstract after you use something twice, and it will keep your development process faster.
When it comes to repeating things, I wait until it's painful. For example, if I use a collapsing list idea in a couple different places, and update it in one place but forget to update the other, it's time to start extracting components.
Sometimes if you're iterating a list of things and need to apply different colors in the same way, you could throw in a data structure to help, or a component if you want a full on abstraction.
Learn to embrace the messiness— and "mess" isn't even the right word. What you're embracing is the coupling between the classes and the HTML.
Here's an example that Adam Wathan uses:
Say you've extracted a Card from your markup. You might think you've reduced CSS duplication since you've got a Card
, CardTitle
, and CardBody
each with a list of @apply
classes.
But the problem is that you still have the need for the actual HTML of the card. So to actually use your component you still need a <section>
or <div>
etc. so there's still a coupling.
This is why Adam suggests using Components for reuse instead of the @apply
directive because the component is reusable and everything is there.
If you find yourself needing something that Tailwind doesn't give you, what's your decision making process?
Some people might think that when they hit something that Tailwind doesn't have or do quite right for them that they'll discount it as a whole because of that 5% or 1% case that isn't covered.
I've become comfortable with bailing out of Tailwind when I need to. For example, on the miragejs.com landing page, there are custom SVG quotes that are offset and scrolling code animations that Tailwind doesn't come with. In these situations, I use inline styles with React.
For one-off things, don't feel pressured to figure out the Tailwind way to do things. The reason we use Tailwind for as much as possible is to make our site feel like it belongs to a consistent design.
There are rare times when I extend Tailwind. For example, think of an animation that nudges an element to the right on hover. Tailwind has a nice API for creating utilities based on their scaling, and you want your animation to have a scale in terms of how dramatic they should be. I would want to think of an API that would feel like it belongs.
Because the Tailwind team is so detail-oriented there are things that you might not have necessarily thought about for things like button focus states, outlines etc.
Where does the development community go after Tailwind?
Since Tailwind boils down to a light abstraction layer over a subset of CSS, when new CSS stuff comes out it will make its way into Tailwind. Then it becomes a matter of learning how to use the CSS feature.
For example, flex-gap
is coming out soon which will change the behavior of how elements wrap without having to do things like negative margin tricks. Aspect ratios are in CSS now, and will make their way into Tailwind.
More CSS rules and properties means more education to make sure people know what they are and how to use them.
The Design System stuff can still be a pain point. When I work on a project that gets big enough, I feel the need to have components for titles, buttons, links, etc. I'd like to see some blessed patterns or ways of doing things emerge for things like "create your own Bootstrap" implemented with Tailwind.
There's a misconception that because you're using Tailwind and have several CSS classes on things it means that you can't have a button component. Really, the opposite is true. It's actually a really good choice when building higher level components.
A library or set of conventions around things so they aren't reinvented every time.