Skip to main content

Challenging the Skeptics: Unveiling the Undeniable Goodness of Tailwind CSS

Β· 6 min read
Nick Taylor
AI Engineer

People definitely have opinions about Tailwind. There are staunch supporters and staunch haters, but I really don't want to get into all that. Head on over to Twitter if you want to waste some time.

If you're pretty well versed with Tailwind, this article might not be for you, but who knows? Read on and maybe you'll learn something.

I'm coming in with what, I think, is a fresh perspective. I'm using Tailwind for the first time professionally. Furthermore, I don't consider myself a CSS expert, but I think I have pretty solid CSS skills.

I mention all this, to convey a sentiment, I've seen many people exhibit. You're using Tailwind because you don't understand CSS. I do understand CSS.

So the first thing that I've seen when people say when they do not like Tailwind, is that it's not CSS, or it's inline CSS. This is completely false, even coming in as a newbie to Tailwind, all Tailwind is, at the end of the day, once it's compiled, is CSS utility classes.

Comparisons​

So let's look at some comparisons between Tailwind and "real" CSS. I'm going to put the vanilla CSS in a style tag, but you could also put it in a .css file and link it in the head of your HTML or however your application bundles CSS. This is just for the sake of comparison.

First Glances of Tailwind​

Vanilla CSS

{% raw %}
<style>
.my-list {
display: flex;
flex-direction: column;
gap: 1.5rem;
}

.my-list li {
border-width: 1px;
}
</style>
<ul class="my-list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
{% endraw %}

Tailwind

{% raw %}
<ul class="flex flex-col gap-6">
<li class="border">Item 1</li>
<li class="border">Item 2</li>
<li class="border">Item 3</li>
</ul>
{% endraw %}

So the first thing someone might say is that Tailwind is repeating the border CSS class on a list item, <li>, instead of using a selector that can target the li DOM elements. This is true, but Tailwind allows you to create the equivalent of .my-list li. You can do the following:

{% raw %}
<ul class="flex flex-col gap-6 [&_li]:border">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
{% endraw %}

This is probably where someone might say, "Well, now you're just writing inline CSS." This is also false. It will generate a CSS rule based on the [&_li]:border CSS class name. It will compile it to literal CSS that will generate an equivalent CSS rule comparable to the CSS rule for the .mylist li selector.

In fact, this is what it compiles to. I've formatted it since it gets minified.

{% raw %}
.\[\&_li\]\:border li {
border-width: 1px;
}
{% endraw %}

You could make an argument that the "real" version looks nicer, but this isn't a strong argument, and you have CSS source maps if you open the browser dev tools.

I'll say it here and repeat it again later. Tailwind is a utility-first CSS framework. It's not inline CSS.

If you want to see an example of this in production grade code, check out a recent pull request (PR) of mine to the OpenSauced app repository.

https://github.com/open-sauced/app/pull/2524

Styling pseudo-elements​

What about something more complex like pseudo-elements? Let's take the ::before pseudo-element for a spin.

Vanilla CSS

{% raw %}
<style>
.pizza-time::before {
content: attr(data-inset-label);
}
</style>
<p data-inset-label="πŸ•" class="pizza-time">OpenSauced is awesome!</p>
{% endraw %}

Tailwind

{% raw %}
<p data-inset-label="πŸ•" class="before:content-[attr(data-inset-label)]">
OpenSauced is awesome!
</p>
{% endraw %}

Here's what it generates as CSS when Tailwind compiles that CSS class.

{% raw %}
.before\:content-\[attr\(data-inset-label\)\]:before{
--tw-content:attr(data-inset-label);
content:var(--tw-content)
}
{% endraw %}

You could complain that that is one hell of a bloated CSS class name, but again, I don't think this is a colossal deal.

If you want to see an example of this in production grade code, check out a recent PR of mine to the OpenSauced app repository.

https://github.com/open-sauced/app/pull/2552

Animations​

If you're looking to add animations, Tailwind ships with plenty of useful animations and CSS classes to leverage them.

Need a custom animation? You can do that as well. I won't go into it here, but here's a great post about writing custom animations in Tailwind.

Accessibility​

You've got all these cool animations, but what if someone has specified prefers-reduced-motion? Tailwind can handle that for you as long as you prefix your animation with motion-safe:, e.g.

{% raw %}
<p class="motion-safe:animate-spin">Spinning text</p>
{% endraw %}

There's other useful Tailwind classes for accessibility, like sr-only, which will remain in the page, but only be visible to screen readers.

I think something that would be interesting to add to the Tailwind a11y story is using Tatiana Mac's (@tatianamac) approach of taking a no-motion-first approach to animations.

Define some base styles​

I'm all for components, and I'm a big fan of JSX. Tailwind pairs nicely with components, but I do think that it's still good to have some base styles defined, even if you are using components.

For example, a base font size and colour, focus state styles, headings etc. This is what I ended up doing in the OpenSauced app repository.

Another Complaint: It's like bootstrap​

Tailwind CSS on its own is not like bootstrap. It's just CSS utility classes, whereas bootstrap is UI components and CSS.

I've never used it, but maybe you could fall into this trap with Tailwind UI.

Tradeoffs​

Like many things, there are tradeoffs. I think the biggest one is learning the Tailwind CSS classes and naming conventions for building them, but I think the benefits outweigh this. And to be honest, once you start writing the classes frequently, the naming convention just sticks in your head.

And if you have some super complex CSS, for whatever reason, Tailwind can't handle, there's nothing wrong with adding some custom CSS.

Wrapping Things Up​

I literally only started using Tailwind September 18th of 2023 when I started at OpenSauced.

Tailwind has made me super productive while building out OpenSauced, and I've used it in some other projects since then.

Remember, Tailwind is a utility-first CSS framework. It's not inline CSS.

I encourage you to give Tailwind a go. They have outstanding documentation and great IDE support to help you along the way.

If you give it a go and say it's not for me, that's OK. Use what makes you the most productive.

Stay saucy peeps!

If you would like to know more about my work in open source, follow me on OpenSauced.

Development Tools and Platforms: Create your Dev.to + Pipedream Automation in under 20 minutes

Β· 5 min read
Bekah Hawrot Weigel
Developer Experience Lead

Today is day 6 of my 29 Days of Open Source Alternatives series, where I'll be exploring open source alternatives to proprietary software in the categories of Game Development and Multimedia, Development Tools and Platforms, Productivity and Collaboration Tools, and more. If you'd like to see the list of the open source alternatives I'll be covering this month, head over to my 29 Days of Open Source Alts Page or check out my bonus lists: open source games and API Development Tools.


A couple of years ago, I was working with a university doctoral student who was recording oral histories. One of the biggest challenges she faced was taking the videos and transcribing them, which was required by the doctoral committee. We came up with a low-code way to solve her problem: the solution was Pipedream.

Development Tools and Platforms: A Closer Look at API Testing with Hoppscotch

Β· 5 min read
Bekah Hawrot Weigel
Developer Experience Lead

Today is day 5 of 29 Days of Open Source Alternatives. You can find the list of most of the projects we're going to cover here. This part of the series will cover Development Tools and Platforms


APIs, or Application Programming Interfaces, allow for communication between different software systems and services, forming the necessary bridge in applicates used daily in industries ranging from banking to entertainment. Because we depend on technology so much, it's important to make sure we're testing to make sure everything works.

Development Tools and Platforms: Open Source, SaaS, and BoxyHQ

Β· 5 min read
Bekah Hawrot Weigel
Developer Experience Lead

Today is day 4 of 29 Days of Open Source Alternatives. You can find the list of most of the projects we're going to cover here. This part of the series will cover Development Tools and Platforms.


We've seen a rise in popularity for Software as a Service (SaaS) tools because of their convenience and flexibility, often handling the management of software distribution, maintenance, and feature development for their users. In fact, you're probably using a SaaS product whether you know it or not. Examples of SaaS dev tools include code editors, project management software, collaboration platforms, and more.

Game Development and Multimedia: Audacity, the Sound of Creativity

Β· 5 min read
Bekah Hawrot Weigel
Developer Experience Lead

Today is day 3 of 29 Days of Open Source Alternatives, and the final post for the Game Development and Multimedia segment.

I haven't used Audacity myself, but anytime I ask a content creator what tools they use for content creation, Audacity is always mentioned. Because I co-host two podcasts, and I'm very invested in finding more ways to talk about storytelling, Audacity is on the top of my list as a tool to explore ways to share my creative process.

Game Development and Multimedia: theatre.js, Unleashing Creativity in Animation and Motion Design

Β· 4 min read
Bekah Hawrot Weigel
Developer Experience Lead

**Today is day 2 of 29 Days of Open Source Alternatives, and in this short week of February, we're covering Game Development and Multimedia. If you'd like to see more open source games, you can check out my OSS Games Page. **

Before coming into tech, I spent a lot more time writing and working creatively, mostly writing screenplay or tv pilot specs. It's always been my dreams to see my stories come alive on screen. You know that crane shot you see in the most epic of all scenes in action movies? That has always been my dream.

Game Development and Multimedia: Godot Engine, A Game-Changer in Game Development

Β· 6 min read
Bekah Hawrot Weigel
Developer Experience Lead

Today starts my 29 Days of Open Source Alternatives series, where I'll be exploring open source alternatives to proprietary software in the categories of Game Development and Multimedia, Development Tools and Platforms, Productivity and Collaboration Tools, and more. If you'd like to see the list of the open source alternatives I'll be covering this month, head over to my 29 Days of Open Source Alts Page or check out my list of open source games.


Last year, we decided to homeschool my 13 year old for a semester. When he told me that he was really interested in game development, I installed all things Unity and tried to get him up and running.

Narrator: we never got things up in running.

Unlocking the Power of HTML's Native Browser Dialog Element

Β· 4 min read
Nick Taylor
AI Engineer

All the major browsers now support the &lt;dialog%gt; element. Why add this HTML element? User land code, code that developers write to fill in gaps of the browser, was doing similar things repeatedly, especially around focus trapping, and browser engines responded by adding this functionality directly in the browser.

Focus Trapping​

What is focus trapping? It's a feature where you do not want focus outside a specific element, and that element typically contains focusable elements.

For example, a form in a modal to confirm an action: As a user uses the keyboard to navigate, they go to the next focusable element, e.g. a button.

If they reach the last focusable element in the modal, without focus trapping, the focus would go to the next focusable element in the document object model (DOM). With focus trapping, you go from the last focusable back to the first focusable element in the parent element.

In user land, popular packages like focus-trap have enabled developers to incorporate focus trapping.

<dialog> for Modal Dialogs​

With the dialog element, you get this for free, although there is a gotcha. If you add a dialog element to the page with the open attribute set, the dialog element will become visible on the page; however, focus trapping will not work as you'd expect in a modal.

From the API documentation:

Note: While you can toggle between the open and closed states of non-modal dialog boxes by toggling the presence of the open attribute, this approach is not recommended.

To get focus trapping working, the JavaScript API is required. You can display a modal on the screen by calling the HTMLDialogElement showModal method.

Note that you'll need to view this CodePen in full view because, for some reason, modal dialog focus trapping does not work in the CodePen editor view.

https://codepen.io/nickytonline/pen/NWJvbPe

Not only do you get focus trapping, you also get modal close functionality that people have come to expect via the Escape key.

All of that is already amazing, but another common thing people were doing in user land was adding a background to block out users from interacting with the page. With the &lt;dialog%gt; element, we can add a ::backdrop pseudo-element that does this for you. All you need to do is style it. In the CodePen above, uncomment out this code in the CSS panel to see this in action.

{% raw %}
dialog::backdrop {
background-color: purple;
opacity: 0.55;
filter: blur(100px);
}
{% endraw %}

<dialog> for Non-Modal Dialogs​

The structure of a non-modal dialog element is the same as a modal dialog. The main difference is to show a non-modal dialog, you need to call the HTMLDialogElement show method.

With a non-modal dialog, the user is not blocked from navigating the rest of the page, i.e. no focus trapping, and the Escape key will not automatically close the dialog.

https://codepen.io/nickytonline/pen/ExMvNJw

Closing a dialog​

To close a dialog or modal, we can use the HTMLDialogElement close method.

{% raw %}
const modal = document.querySelector("dialog");

// some button in the dialog that has a click event listener registered
modal.querySelector("button").addEventListener("click", () => {
modal.close();
});
{% endraw %}

Wrapping up​

The web platform keeps getting better. It's great to see pain points in user land that had user solutions come natively to browser land.

References​

Stay saucy peeps!

If you would like to know more about my work in open source, follow me on OpenSauced.

Take the First Step: the Open Source Game

Β· 4 min read
Bekah Hawrot Weigel
Developer Experience Lead

There's been a lot of talk recently about how to get into open source. I think part of the challenge of breaking into open source is overthinking it.

https://x.com/BekahHW/status/1742920548353745068?s=20

We need to reframe how we think about starting with contributing. Getting started with open source can feel intimidating, but it's just like learning a new game with complex rules. You wouldn't jump straight into a game without reading the instructions. In the same way, you shouldn't overwhelm yourself with open source. Take it step-by-step.

Migrating from Jest to Vitest for your React Application

Β· 5 min read
Nick Taylor
AI Engineer

Are you looking to migrate from Jest to Vitest for your React application? Look no further.

I recently migrated the OpenSauced app repository to Vitest. Here's the pull request if you're interested.

https://github.com/open-sauced/app/pull/2296

Why move from Jest to Vitest?​

Both Jest and Vitest are great testing frameworks, so why bother switching?

Vitest supports ECMAScript modules (ESM), TypeScript out of the box.

Jest requires additional setup for both, although there is experimental support for ESM.

Vitest is also fast. Yes, it depends, but in general, it's faster. (See the Vitest comparison with other test runners)

Neo fighting an agent in the Matrix movie with one hand

If you're already using Vite in your project or the meta-framework you're using is based on Vite, using Vitest is a no-brainer as you're already in the Vite ecosystem.

If your project isn't using Vite, e.g. Next.js, it's still a great move.

Vitest makes it effortless to migrate from Jest. It supports the same Jasmine like API.

TLDR; You don't need to update existing tests, as it’s mostly a drop-in replacement for Jest.

Some other niceties are a default watch mode care of Vite instant Hot Module Reload (HMR).

Install Vitest​

The first thing you want to do is install Vitest.

https://github.com/vitest-dev/vitest

Run npm install vitest -D in the terminal to install Vitest as a dev dependency.

Next up, create a vitest.config.ts file in the root of your project. Even if you're not using TypeScript, name it vitest.config.ts.

In that file, add the following code and save it.

{% raw %}
import { defineConfig } from "vite";

// https://vitejs.dev/config/
export default defineConfig({
test: {
// some paths to the files that are test files
include: ["./**/*.test.ts", "./**/*.test.tsx"],
},
});
{% endraw %}

You can explicitly import describe, it/test, expect or you can have it work like in Jest where they're all globals. All you need to do is set globals to true in the Vitest configuration.

{% raw %}
import { defineConfig } from "vite";

// https://vitejs.dev/config/
export default defineConfig({
test: {
include: ["./**/*.test.ts", "./**/*.test.tsx"],
+ globals: true,
},
});
{% endraw %}

Using Vitest with React​

At OpenSauced, we're using Next.js to build out the main application.

Vitest is based off Vite which supports React via their plugin ecosystem, so you'll need to install the Vite React plugin to get React support.

Run npm install @vitejs/plugin-react -D to install the plugin as a dev dependency.

Update the Vitest configuration to add the React plugin.

{% raw %}
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// https://vitejs.dev/config/
export default defineConfig({
+ plugins: [react()],
test: {
include: ["./**/*.test.ts", "./**/*.test.tsx"],
globals: true,
},
});
{% endraw %}

React Testing Library​

If you happen to be using React Testing Library in your project, you'll need to keep the jsdom dev dependency installed.

Next, add jsdom to your Vitest configuration.

{% raw %}
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
test: {
include: ["./**/*.test.ts", "./**/*.test.tsx"],
globals: true,
+ environment: "jsdom",
},
});
{% endraw %}

Aliases​

Your project might be using aliases for paths. For example, in the OpenSauced app repository, components, lib, and img are aliases to folders.

If you need to support aliases, Vitest has you covered.

Here's an example of supporting the above-mentioned aliases.

{% raw %}
export default defineConfig({
plugins: [react()],
+ resolve: {
+ alias: {
+ components: fileURLToPath(new URL("./components", import.meta.url)),
+ lib: fileURLToPath(new URL("./lib", import.meta.url)),
+ img: fileURLToPath(new URL("./img", import.meta.url)),
+ },
+ },
test: {
include: ["./**/*.test.ts", "./**/*.test.tsx"],
globals: true,
environment: "jsdom",
},
});
{% endraw %}

TypeScript Types​

If you're using TypeScript, you can add the types for Vitest to the project.

In your tsconfig.json file, add the types in the compiler options section of the TypeScript configuration file.

{% raw %}
{
"compilerOptions": {
// . .. other compiler options in your project
+ "types": ["vitest/globals"]
}

// . .. other TypeScript configuration options in your project
}

{% endraw %}

Running Tests​

To run tests using Vitest, you can run vitest. By default, it will go into watch mode. If you only want to run the test suite once, e.g. for the CI/CD pipeline, run vitest run.

Removing Jest​

If your project is a TypeScript project, you probably have the types for Jest in your project. If you do, run the following to remove the Jest TypeScript types.

{% raw %}
npm uninstall -D @types/jest
{% endraw %}

Uninstall Jest itself.

{% raw %}
npm uninstall jest jest-environment-jsdom -D
{% endraw %}

And that's it! Happy testing!

Stay saucy peeps!

If you would like to know more about my work in open source, follow me on OpenSauced.