How using Tailwind enhances your coding skills
Fernando González Tostado
Posted on January 24, 2023
Recently, there has been a lot of debate among developers in social media about the effectiveness of Tailwind in improving developer experience (DX) when styling components. Some argue that it is just inline styling using classes, and that it can clutter your HTML markup with long class names.
Ok, enough memes. Let's get serious.
Personally, I have used Tailwind in my recent personal projects. My initial experience was mixed. I found that I was repeating styling for many elements and my coding speed was slower due to the unfamiliarity with the naming conventions. I often found myself needing to look up basic selectors for margins, padding, and widths.
A simple styled button would end up like this, with a very verbose className
.
<button
className="absolute top-0 bottom-0 -right-14
flex items-center justify-center border-0 p-0 text-center
hover:no-underline hover:outline-none focus:no-underline focus:outline-none"
type="button"
/>
Using BEM and scss this button would look like this:
<button className="carousel__control--next"/>
Obviously the carousel__control–next
class won't add any style by itself. I had to have that selector somewhere in my source:
// somewhere in my scss
// src/styles/components/_carousel.scss
.carousel__control--next {
top: 0;
bottom: 0;
right: -14px;
border: 0;
padding: 0;
text-align: center;
&:hover {
text-decoration: none;
outline: none;
}
&:focus {
text-decoration: none;
outline: none;
}
}
I found that using Tailwind did not save me lines of code, but rather added them directly to my elements. Although I was starting to remember the most commonly used class names, it did not improve my developer experience as it only eliminated the need for .scss files.
I was accustomed to separating styling into a separate directory, following an atomic design approach, which made my code appear cleaner and helped me organize my components and their respective styles.
In conclusion, I did not see a significant reduction in lines of code when using Tailwind, but I believed that the real benefit of using it would be the ability to memorize the most frequently used classes. This would help me add extra details to my components and make them look more polished. For example:
// using Tailwind and BEM
<button className="carousel__control--next mx-auto my-12 md:my-4 w-full md:w-1/2"/>
Wow, that's the best of both worlds, isn't it?!
NO, I was completely missing the big picture.
While you can mix selectors and tailwind classname, or add Tailwind classnames to your selectors using @apply
in your files to write mixed CSS:
.carousel__control--next {
// the rest of the styles
@apply mx-auto my-12 md:my-4 w-full md:w-1/2;
}
It is not really meant to be used for removing HTML markup from your elements, but for overriding styles from a third party library. For example, this Menu.Item
component from Semantic UI React:
// overriding a semantic ui button
import { Menu } from 'semantic-ui-react';
<Menu.Item />
// overriding the Item styles with tailwind
.ui.secondary.pointing.menu .item {
@apply mx-auto my-12 md:my-4 w-full md:w-1/2;
}
In using Tailwind, the benefit is not just in saving lines of code, but also in not having to spend time thinking about class names and jumping between files. By focusing on reusability, which is closely tied to important coding best practices, Tailwind can help enhance our coding skills.
How does Tailwind could actually push us to improve my coding skills?
Have you heard of the SOLID principles? It is basically a set of rules that should help us to make our code more functional, maintainable and robust.
What do SOLID principles have to do with Tailwind?
Both strive for reusability and separation of concerns.
*SOLID and Tailwind
*
Let's start with the first and most important SOLID principle: the Single Responsibility Principle, which encourages us as developers to write components that do only one thing.
Let's say we have a button component, we intend that button to be used for firing the onClick event, or if it's a form button, a submit event, etc:
// a button that accepts onClick, className, type and children props
const Button = ({ onClick, className, type, children, ...props }) => (
<button
onClick={onClick}
className={className}
type={type}
{...props}
>
{children}
</button>
);
Now, let's say that we want this button to be our main button for our app. Here's where Tailwind comes in.
const Button = ({ onClick, className, type, children, ...props }) => (
<button
onClick={onClick}
className={`mx-auto my-12 md:my-4 py-2 px-4
w-full md:w-1/2 bg-blue-500 font-bold text-white
flex items-center justify-center rounded
hover:bg-secondary-blue hover:no-underline hover:outline-none focus:no-underline focus:outline-none
${className}`}
type={type}
{...props}
>
{children}
</button>
);
We now add the fixed classes and accept a className
prop, for adapting our button for the specific needs, saving us a lot of lines of code. Also, if we would need to update the styling of the button, we could safely do it, without fear of breaking other components in the way, which is something that could easily happen if we were updating the css file names directly.
By creating a reusable Button
component, we might be also forced to think of making use of the atomic design folder structure, where we group our components from generic to specific.
src
├── components
│ ├── atoms
│ │ ├── button.tsx
│ │ ├── link.tsx
│ │ ├── index.ts
│ │ └── ...
│ ├── molecules
│ │ ├── index.ts
│ │ └── ...
│ ├── organisms
│ │ ├── index.ts
│ │ └── ...
│ ├── templates
│ │ ├── index.ts
│ │ └── ...
And When using atomic design we indirectly strive for the Open/Closed Principle, where now that we have smaller items, we can use them as part of a bigger one.
For example, in this Page component:
// this a page component
import { Container, Header } from 'src/components/atoms'
import { ProductSearchbar } from 'src/components/molecules'
import { Table } from 'sr/components/organisms'
return (
<Container className='text-center'>
<Header className='3xl'>Hello!</Header>
<Header className='xl'>I'm a page component</Header>
<ProductSearchbar className='w-1/2 md:w-1/3 mx-auto' />
<Table data={tableItems} className='w-full' />
</Container>
)
Finally, the third SOLID principle involved: the Dependency Inversion Principle, which basically tells us that the components should not care of the implementation details, a higher in the tree level component should handle that:
import { Button } from 'src/components/atoms'
import { Container } from 'src/components/atoms'
import { SomeComponentThatUsesData } from 'src/components/organisms'
export const SomeComponentWithButtons = () => {
const [data, setData] = useState(null);
const handleFetchData = async () => {
// fetch data
const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const data = await res.json();
setData(data);
}
const handleDeleteData = () => {
// delete data
setData(null);
}
return (
<Container className='my-2 md:my-12 w-full md:w-1/2'>
<Button onClick={handleFetchData} className='bg-blue-500'>Fetch data</Button>
<Button onClick={handleDeleteData} className='bg-red-500'>Delete data</Button>
<SomeComponentThatUsesData data={data} />
</Container>
)
}
The SomeComponentWithButtons
is a component that handles some data in state, that data is then passed to SomeComponentThatUsesData
.
As you can see, the buttons don't care about the implementation details, they just know that they receive an onClick prop that is triggered when they are clicked. Whatever is done —and how— it's irrelevant for the button.
The same goes for the SomeComponentThatUsesData
component. It can be reused in several other parts of our code without caring how the data
received is obtained, while the data
is of the type that it requires.
Conclusion
Tailwind is a powerful tool that can greatly enhance your coding skills if you fully embrace and adhere to its core principles. Its emphasis on reusable elements not only promotes clean, maintainable code but also can help enforce SOLID principles without you even realizing it.
However, the ultimate level of power that Tailwind can bring to your development process ultimately depends on your willingness to fully utilize and commit to its recommended practices.
While I have only touched on the basics of the SOLID principles, they can be as complex as you want to make them. By integrating them with Tailwind, you have a great opportunity to fully understand and implement them in your development process. Understanding and utilizing SOLID principles can greatly advance your career as a developer.
Sources
- Atomic Design
- SOLID
- How tailwind adds value
- Foto de Mia Baker en Unsplash
Posted on January 24, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.