tailwind – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Wed, 24 Aug 2022 13:11:48 +0000 en-US hourly 1 https://wordpress.org/?v=6.1.1 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 tailwind – CSS-Tricks https://css-tricks.com 32 32 45537868 Using CSS Cascade Layers to Manage Custom Styles in a Tailwind Project https://css-tricks.com/using-css-cascade-layers-to-manage-custom-styles-in-a-tailwind-project/ https://css-tricks.com/using-css-cascade-layers-to-manage-custom-styles-in-a-tailwind-project/#comments Wed, 24 Aug 2022 13:11:47 +0000 https://css-tricks.com/?p=372576 If a utility class only does one thing, chances are you don’t want it to be overridden by any styles coming from elsewhere. One approach is to use !important to be 100% certain the style will be applied, regardless of …


Using CSS Cascade Layers to Manage Custom Styles in a Tailwind Project originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
If a utility class only does one thing, chances are you don’t want it to be overridden by any styles coming from elsewhere. One approach is to use !important to be 100% certain the style will be applied, regardless of specificity conflicts.

The Tailwind config file has an !important option that will automatically add !important to every utility class. There’s nothing wrong with using !important this way, but nowadays there are better ways to handle specificity. Using CSS Cascade Layers we can avoid the heavy-handed approach of using !important.

Cascade layers allow us to group styles into “layers”. The precedence of a layer always beats the specificity of a selector. Specificity only matters inside each layer. Establishing a sensible layer order helps avoid styling conflicts and specificity wars. That’s what makes CSS Cascade Layers a great tool for managing custom styles alongside styles from third-party frameworks, like Tailwind.

A Tailwind source .css file usually starts something like this:

@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind variants;

Let’s take a look at the official Tailwind docs about directives:

Directives are custom Tailwind-specific at-rules you can use in your CSS that offer special functionality for Tailwind CSS projects. Use the @tailwind directive to insert Tailwind’s base, components, utilities and variants styles into your CSS.

In the output CSS file that gets built, Tailwind’s CSS reset — known as Preflight — is included first as part of the base styles. The rest of base consists of CSS variables needed for Tailwind to work. components is a place for you to add your own custom classes. Any utility classes you’ve used in your markup will appear next. Variants are styles for things like hover and focus states and responsive styles, which will appear last in the generated CSS file.

The Tailwind @layer directive

Confusingly, Tailwind has its own @layer syntax. This article is about the CSS standard, but let’s take a quick look at the Tailwind version (which gets compiled away and doesn’t end up in the output CSS). The Tailwind @layer directive is a way to inject your own extra styles into a specified part of the output CSS file.

For example, to append your own styles to the base styles, you would do the following:

@layer base {
  h1 {
    font-size: 30px;
  }
}

The components layer is empty by default — it’s just a place to put your own classes. If you were doing things the Tailwind way, you’d probably use @apply (although the creator of Tailwind recently advised against it), but you can also write classes the regular way:

@layer components {
  .btn-blue {
    background-color: blue;
    color: white;
  }
}

The CSS standard is much more powerful. Let’s get back to that…

Using the CSS standard @layer

Here’s how we can rewrite this to use the CSS standard @layer:

@layer tailwind-base, my-custom-styles, tailwind-utilities;

@layer tailwind-base {
  @tailwind base;
}

@layer tailwind-utilities {
  @tailwind utilities;
  @tailwind variants;
} 

Unlike the Tailwind directive, these don’t get compiled away. They’re understood by the browser. In fact, DevTools in Edge, Chrome, Safari, and Firefox will even show you any layers you’ve defined.

CSS Cascade Layers with Tailwind CSS layers in DevTools.

You can have as many layers as you want — and name them whatever you want — but in this example, all my custom styles are in a single layer (my-custom-styles). The first line establishes the layer order:

@layer tailwind-base, my-custom-styles, tailwind-utilities;

This needs to be provided upfront. Be sure to include this line before any other code that uses @layer. The first layer in the list will be the least powerful, and the last layer in the list will be the most powerful. That means tailwind-base is the least powerful layer and any code in it will be overridden by all the subsequent layers. That also means tailwind-utilities will always trump any other styles — regardless of source order or specificity. (Utilities and variants could go in separate layers, but the maintainers of Tailwind will ensure variants always trump utilities, so long as you include the variants below the utilities directive.)

Anything that isn’t in a layer will override anything that is in a layer (with the one exception being styles that use !important). So, you could also opt to leave utilities and variants outside of any layer:

@layer tailwind-base, tailwind-components, my-custom-styles;

@layer tailwind-base {
  @tailwind base;
}

@layer tailwind-components {
  @tailwind components;
}

@tailwind utilities;
@tailwind variants;

What did this actually buy us? There are plenty of times when advanced CSS selectors come in pretty handy. Let’s create a version of :focus-within that only responds to keyboard focus rather than mouse clicks using the :has selector (which lands in Chrome 105). This will style a parent element when any of its children receive focus. Tailwind 3.1 introduced custom variants — e.g. <div class="[&:has(:focus-visible)]:outline-red-600"> — but sometimes it’s easier to just write CSS:

@layer tailwind-base, my-custom-styles;
@layer tailwind-base {
  @tailwind base;
}

@tailwind utilities;

@layer my-custom-styles {
  .radio-container {
    padding: 4px 24px;
    border: solid 2px rgb(230, 230, 230);
  }
  .radio-container:has(:focus-visible) {
    outline: solid 2px blue;
  }
}

Let’s say in just one instance we want to override the outline-color from blue to something else. Let’s say the element we’re working with has both the Tailwind class .outline-red-600 and our own .radio-container:has(:focus-visible) class:

<div class="outline-red-600 radio-container"> ... </div>

Which outline-color will win?

Ordinarily, the higher specificity of .radio-container:has(:focus-visible) would mean the Tailwind class has no effect — even if it’s lower in the source order. But, unlike the Tailwind @layer directive that relies on source order, the CSS standard @layer overrules specificity.

As a result, we can use complex selectors in our own custom styles but still override them with Tailwind’s utility classes when we need to — without having to resort to heavy-handed !important usage to get what we want.


Using CSS Cascade Layers to Manage Custom Styles in a Tailwind Project originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/using-css-cascade-layers-to-manage-custom-styles-in-a-tailwind-project/feed/ 2 372576
Adding Tailwind CSS to New and Existing WordPress Themes https://css-tricks.com/adding-tailwind-css-to-wordpress-themes/ https://css-tricks.com/adding-tailwind-css-to-wordpress-themes/#comments Wed, 20 Apr 2022 15:30:29 +0000 https://css-tricks.com/?p=364880 In the 15 or so years since I started making WordPress websites, nothing has had more of an impact on my productivity — and my ability to enjoy front-end development — than adding Tailwind CSS to my workflow (and it …


Adding Tailwind CSS to New and Existing WordPress Themes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
In the 15 or so years since I started making WordPress websites, nothing has had more of an impact on my productivity — and my ability to enjoy front-end development — than adding Tailwind CSS to my workflow (and it isn’t close).

When I began working with Tailwind, there was an up-to-date, first-party repository on GitHub describing how to use Tailwind with WordPress. That repository hasn’t been updated since 2019. But that lack of updates isn’t a statement on Tailwind’s utility to WordPress developers. By allowing Tailwind to do what Tailwind does best while letting WordPress still be WordPress, it’s possible to take advantage of the best parts of both platforms and build modern websites in less time.

The minimal setup example in this article aims to provide an update to that original setup repository, revised to work with the latest versions of both Tailwind and WordPress. This approach can be extended to work with all kinds of WordPress themes, from a forked default theme to something totally custom.

Why WordPress developers should care about Tailwind

Before we talk about setup, it’s worth stepping back and discussing how Tailwind works and what that means in a WordPress context.

Tailwind allows you to style HTML elements using pre-existing utility classes, removing the need for you to write most or all of your site’s CSS yourself. (Think classes like hidden for display: hidden or uppercase for text-transform: uppercase.) If you’ve used frameworks like Bootstrap and Foundation in the past, the biggest difference you’ll find with Tailwind CSS is its blank-slate approach to design combined with the lightness of being CSS-only, with just a CSS reset included by default. These properties allow for highly optimized sites without pushing developers towards an aesthetic built into the framework itself.

Also unlike many other CSS frameworks, it’s infeasible to load a “standard” build of Tailwind CSS from an existing CDN. With all of its utility classes included, the generated CSS file would simply be too large. Tailwind offers a “Play CDN,” but it’s not meant for production, as it significantly reduces Tailwind’s performance benefits. (It does come in handy, though, if you want to do some rapid prototyping or otherwise experiment with Tailwind without actually installing it or setting up a build process.)

This need to use Tailwind’s build process to create a subset of the framework’s utility classes specific to your project makes it important to understand how Tailwind decides which utility classes to include, and how this process affects the use of utility classes in WordPress’s editor.

And, finally, Tailwind’s aggressive Preflight (its version of a CSS reset) means some parts of WordPress are not well-suited to the framework with its default settings.

Let’s begin by looking at where Tailwind works well with WordPress.

Where Tailwind and WordPress work well together

In order for Tailwind to work well without significant customization, it needs to act as the primary CSS for a given page; this eliminates a number of use cases within WordPress.

If you’re building a WordPress plugin and you need to include front-end CSS, for example, Tailwind’s Preflight would be in direct conflict with the active theme. Similarly, if you need to style the WordPress administration area — outside of the editor — the administration area’s own styles may be overridden.

There are ways around both of these issues: You can disable Preflight and add a prefix to all of your utility classes, or you could use PostCSS to add a namespace to all of your selectors. Either way, your configuration and workflow are going to get more complicated.

But if you’re building a theme, Tailwind is an excellent fit right out of the box. I’ve had success creating custom themes using both the classic editor and the block editor, and I’m optimistic that as full-site editing matures, there will be a number of full-site editing features that work well alongside Tailwind.

In her blog post “Gutenberg Full Site Editing does not have to be full,” Tammie Lister describes full-site editing as a set of separate features that can be adopted in part or in full. It’s unlikely full-site editing’s Global Styles functionality will ever work with Tailwind, but many other features probably will.

So: You’re building a theme, Tailwind is installed and configured, and you’re adding utility classes with a smile on your face. But will those utility classes work in the WordPress editor?

With planning, yes! Utility classes will be available to use in the editor so long as you decide which ones you’d like to use in advance. You’re unable to open up the editor and use any and all Tailwind utility classes; baked into Tailwind’s emphasis on performance is the limitation of only including the utility classes your theme uses, so you need to let Tailwind know in advance which ones are required in the editor despite them being absent elsewhere in your code.

There are a number of ways to do this: You can create a safelist within your Tailwind configuration file; you can include comments containing lists of classes alongside the code for custom blocks you’ll want to style in the block editor; you could even just create a file listing all of your editor-specific classes and tell Tailwind to include it as one of the source files it monitors for class names.

The need to commit to editor classes in advance has never held me back in my work, but this remains the aspect of the relationship between Tailwind and WordPress I get asked about the most.

A minimal WordPress theme with a minimal Tailwind CSS integration

Let’s start with the most basic WordPress theme possible. There are only two required files:

  • style.css
  • index.php

We’ll generate style.css using Tailwind. For index.php, let’s start with something simple:

<!doctype html>
<html lang="en">
  <head>
    <?php wp_head(); ?>
    <link rel="stylesheet" href="<?php echo get_stylesheet_uri(); ?>" type="text/css" media="all" />
  </head>
  <body>
    <?php
    if ( have_posts() ) {
      while ( have_posts() ) {
        the_post();
        the_title( '<h1 class="entry-title">', '</h1>' );
        ?>
        <div class="entry-content">
          <?php the_content(); ?>
        </div>
        <?php
      }
    }
    ?>
  </body>
</html>

There are a lot of things a WordPress theme should do that the above code doesn’t — things like pagination, post thumbnails, enqueuing stylesheets instead of using link elements, and so on — but this will be enough to display a post and test that Tailwind is working as it should.

On the Tailwind side, we need three files:

  • package.json
  • tailwind.config.js
  • An input file for Tailwind

Before we go any further, you’re going to need npm. If you’re uncomfortable working with it, we have a beginner’s guide to npm that is a good place to start!

Since there is no package.json file yet, we’ll create an empty JSON file in the same folder with index.php by running this command in our terminal of choice:

echo {} > ./package.json

With this file in place, we can install Tailwind:

npm install tailwindcss --save-dev

And generate our Tailwind configuration file:

npx tailwindcss init

In our tailwind.config.js file, all we need to do is tell Tailwind to search for utility classes in our PHP files:

module.exports = {
  content: ["./**/*.php"],
  theme: {
    extend: {},
  },
  plugins: [],
}

If our theme used Composer, we’d want to ignore the vendor directory by adding something like "!**/vendor/**" to the content array. But if all of your PHP files are part of your theme, the above will work!

We can name our input file anything we want. Let’s create a file called tailwind.css and add this to it:

/*!
Theme Name: WordPress + Tailwind
*/

@tailwind base;
@tailwind components;
@tailwind utilities;

The top comment is required by WordPress to recognize the theme; the three @tailwind directives add each of Tailwind’s layers.

And that’s it! We can now run the following command:

npx tailwindcss -i ./tailwind.css -o ./style.css --watch

This tells the Tailwind CLI to generate our style.css file using tailwind.css as the input file. The --watch flag will continuously rebuild the style.css file as utility classes are added or removed from any PHP file in our project repository.

That’s as simple as a Tailwind-powered WordPress theme could conceivably be, but it’s unlikely to be something you’d ever want to deploy to production. So, let’s talk about some pathways to a production-ready theme.

Adding TailwindCSS to an existing theme

There are two reasons why you might want to add Tailwind CSS to an existing theme that already has its own vanilla CSS:

  • To experiment with adding Tailwind components to an already styled theme
  • To transition a theme from vanilla CSS to Tailwind

We’ll demonstrate this by installing Tailwind inside Twenty Twenty-One, the WordPress default theme. (Why not Twenty Twenty-Two? The most recent WordPress default theme is meant to showcase full-site editing and isn’t a good fit for a Tailwind integration.)

To start, you should download and install the theme in your development environment if it isn’t installed there. We only need to follow a handful of steps after that:

  • Navigate to the theme folder in your terminal.
  • Because Twenty Twenty-One already has its own package.json file, install Tailwind without creating a new one:
npm install tailwindcss --save-dev
  • Add your tailwind.config.json file:
npx tailwindcss init
  • Update your tailwind.config.json file to look the same as the one in the previous section.
  • Copy Twenty Twenty-One’s existing style.css file to tailwind.css.

Now we need to add our three @tailwind directives to the tailwind.css file. I suggest structuring your tailwind.css file as follows:

/* The WordPress theme file header goes here. */

@tailwind base;

/* All of the existing CSS goes here. */

@tailwind components;
@tailwind utilities;

Putting the base layer immediately after the theme header ensures that WordPress continues to detect your theme while also ensuring the Tailwind CSS reset comes as early in the file as possible.

All of the existing CSS follows the base layer, ensuring that these styles take precedence over the reset.

And finally, the components and utilities layers follow so they can take precedence over any CSS declarations with the same specificity.

And now, as with our minimal theme, we’ll run the following command:

npx tailwindcss -i ./tailwind.css -o ./style.css --watch

With your new style.css file now being generated each time you change a PHP file, you should check your revised theme for minor rendering differences from the original. These are caused by Tailwind’s CSS reset, which resets things a bit further than some themes might expect. In the case of Twenty Twenty-One, the only fix I made was to add text-decoration-line: underline to the a element.

With that rendering issue resolved, let’s add the Header Banner Component from Tailwind UI, Tailwind’s first-party component library. Copy the code from the Tailwind UI site and paste it immediately following the “Skip to content” link in header.php:

Showing a Tailwind CSS component on the front end of a WordPress theme.

Pretty good! Because we’re now going to want to use utility classes to override some of the existing higher-specificity classes built into the theme, we’re going to add a single line to the tailwind.config.js file:

module.exports = {
  important: true,
  content: ["./**/*.php"],
  theme: {
    extend: {},
  },
  plugins: [],
}

This marks all Tailwind CSS utilities as !important so they can override existing classes with a higher specificity. (I’ve never set important to true in production, but I almost certainly would if I were in the process of converting a site from vanilla CSS to Tailwind.)

With a quick no-underline class added to the “Learn more” link and bg-transparent and border-0 added to the dismiss button, we’re all set:

Showing a Tailwind CSS component in the front end of a WordPress theme, but with more refined styles for the buttons and links.

It looks a bit jarring to see Tailwind UI’s components merged into a WordPress default theme, but it’s a great demonstration of Tailwind components and their inherent portability.

Starting from scratch

If you’re creating a new theme with Tailwind, your process will look a lot like the minimal example above. Instead of running the Tailwind CLI directly from the command line, you’ll probably want to create separate npm scripts for development and production builds, and to watch for changes. You may also want to create a separate build specifically for the WordPress editor.

If you’re looking for a starting point beyond the minimal example above — but not so far beyond that it comes with opinionated styles of its own — I’ve created a Tailwind-optimized WordPress theme generator inspired by Underscores (_s), once the canonical WordPress starter theme. Called _tw, this is the quick-start I wish I had when I first combined Tailwind with WordPress. It remains the first step in all of my client projects.

If you’re willing to go further from the structure of a typical WordPress theme and add Laravel Blade templates to your toolkit, Sage is a great choice, and they have a setup guide specific to Tailwind to get you started.


However you choose to begin, I encourage you to take some time to acclimate yourself to Tailwind CSS and to styling HTML documents using utility classes: It may feel unusual at first, but you’ll soon find yourself taking on more client work than before because you’re building sites faster than you used to — and hopefully, like me, having more fun doing it.


Adding Tailwind CSS to New and Existing WordPress Themes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/adding-tailwind-css-to-wordpress-themes/feed/ 17 364880
Building a Tennis Trivia App With Next.js and Netlify https://css-tricks.com/building-a-tennis-trivia-app-with-next-js-and-netlify/ https://css-tricks.com/building-a-tennis-trivia-app-with-next-js-and-netlify/#comments Fri, 08 Oct 2021 14:30:57 +0000 https://css-tricks.com/?p=353066 Today we will be learning how to build a tennis trivia app using Next.js and Netlify. This technology stack has become my go-to on many projects. It allows for rapid development and easy deployment.

Without further ado let’s jump in!…


Building a Tennis Trivia App With Next.js and Netlify originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Today we will be learning how to build a tennis trivia app using Next.js and Netlify. This technology stack has become my go-to on many projects. It allows for rapid development and easy deployment.

Without further ado let’s jump in!

What we’re using

  • Next.js
  • Netlify
  • TypeScript
  • Tailwind CSS

Why Next.js and Netlify

You may think that this is a simple app that might not require a React framework. The truth is that Next.js gives me a ton of features out of the box that allow me to just start coding the main part of my app. Things like webpack configuration, getServerSideProps, and Netlify’s automatic creation of serverless functions are a few examples.

Netlify also makes deploying a Next.js git repo super easy. More on the deployment a bit later on.

What we’re building

Basically, we are going to build a trivia game that randomly shows you the name of a tennis player and you have to guess what country they are from. It consists of five rounds and keeps a running score of how many you get correct.

The data we need for this application is a list of players along with their country. Initially, I was thinking of querying some live API, but on second thought, decided to just use a local JSON file. I took a snapshot from RapidAPI and have included it in the starter repo.

The final product looks something like this:

You can find the final deployed version on Netlify.

Starter repo tour

If you want to follow along you can clone this repository and then go to the start branch:

git clone git@github.com:brenelz/tennis-trivia.git
cd tennis-trivia
git checkout start

In this starter repo, I went ahead and wrote some boilerplate to get things going. I created a Next.js app using the command npx create-next-app tennis-trivia. I then proceeded to manually change a couple JavaScript files to .ts and .tsx. Surprisingly, Next.js automatically picked up that I wanted to use TypeScript. It was too easy! I also went ahead and configured Tailwind CSS using this article as a guide.

Enough talk, let’s code!

Initial setup

The first step is setting up environment variables. For local development, we do this with a .env.local file. You can copy the .env.sample from the starter repo.

cp .env.sample .env.local

Notice it currently has one value, which is the path of our application. We will use this on the front end of our app, so we must prefix it with NEXT_PUBLIC_.

Finally, let’s use the following commands to install the dependencies and start the dev server: 

npm install
npm run dev

Now we access our application at http://localhost:3000. We should see a fairly empty page with just a headline:

Creating the UI markup

In pages/index.tsx, let’s add the following markup to the existing Home() function:

export default function Home() {
  return (
    <div className="bg-blue-500">
    <div className="max-w-2xl mx-auto text-center py-16 px-4 sm:py-20 sm:px-6 lg:px-8">
      <h2 className="text-3xl font-extrabold text-white sm:text-4xl">
        <span className="block">Tennis Trivia - Next.js Netlify</span>
      </h2>
      <div>
        <p className="mt-4 text-lg leading-6 text-blue-200">
          What country is the following tennis player from?
        </p>
        <h2 className="text-lg font-extrabold text-white my-5">
          Roger Federer
        </h2>

        <form>
          <input
            list="countries"
            type="text"
            className="p-2 outline-none"
            placeholder="Choose Country"
          />
          <datalist id="countries">
            <option>Switzerland</option>
           </datalist>
           <p>
             <button
               className="mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-blue-600 bg-white hover:bg-blue-50 sm:w-auto"
               type="submit"
             >
               Guess
            </button>
          </p>
        </form>

        <p className="mt-4 text-lg leading-6 text-white">
          <strong>Current score:</strong> 0
        </p>
      </div>
    </div>
    </div>
  );

This forms the scaffold for our UI. As you can see, we are using lots of utility classes from Tailwind CSS to make things look a little prettier. We also have a simple autocomplete input and a submit button. This is where you will select the country you think the player is from and then hit the button. Lastly, at the bottom, there is a score that changes based on correct or incorrect answers.

Setting up our data

If you take a look at the data folder, there should be a tennisPlayers.json with all the data we will need for this application. Create a lib folder at the root and, inside of it, create a players.ts file. Remember, the .ts extension is required since is a TypeScript file. Let’s define a type that matches our JSON data..

export type Player = {
  id: number,
  first_name: string,
  last_name: string,
  full_name: string,
  country: string,
  ranking: number,
  movement: string,
  ranking_points: number,
};

This is how we create a type in TypeScript. We have the name of the property on the left, and the type it is on the right. They can be basic types, or even other types themselves.

From here, let’s create specific variables that represent our data:

export const playerData: Player[] = require("../data/tennisPlayers.json");
export const top100Players = playerData.slice(0, 100);

const allCountries = playerData.map((player) => player.country).sort();
export const uniqueCountries = [...Array.from(new Set(allCountries))];

A couple things to note is that we are saying our playerData is an array of Player types. This is denoted by the colon followed by the type. In fact, if we hover over the playerData we can see its type:

In that last line we are getting a unique list of countries to use in our country dropdown. We pass our countries into a JavaScript Set, which gets rid of the duplicate values. We then create an array from it, and spread it into a new array. It may seem unnecessary but this was done to make TypeScript happy.

Believe it or not, that is really all the data we need for our application!

Let’s make our UI dynamic!

All our values are hardcoded currently, but let’s change that. The dynamic pieces are the tennis player’s name, the list of countries, and the score.

Back in pages/index.tsx, let’s modify our getServerSideProps function to create a list of five random players as well as pull in our uniqueCountries variable.

import { Player, uniqueCountries, top100Players } from "../lib/players";
...
export async function getServerSideProps() {
  const randomizedPlayers = top100Players.sort((a, b) => 0.5 - Math.random());
  const players = randomizedPlayers.slice(0, 5);

  return {
    props: {
      players,
      countries: uniqueCountries,
    },
  };
}

Whatever is in the props object we return will be passed to our React component. Let’s use them on our page:

type HomeProps = {
  players: Player[];
  countries: string[];
};

export default function Home({ players, countries }: HomeProps) {
  const player = players[0];
  ...
} 

As you can see, we define another type for our page component. Then we add the HomeProps type to the Home() function. We have again specified that players is an array of the Player type.

Now we can use these props further down in our UI. Replace “Roger Federer” with {player.full_name} (he’s my favorite tennis player by the way). You should be getting nice autocompletion on the player variable as it lists all the property names we have access to because of the types that we defined.

Further down from this, let’s now update the list of countries to this:

<datalist id="countries">
  {countries.map((country, i) => (
    <option key={i}>{country}</option>
  ))}
</datalist>

Now that we have two of the three dynamic pieces in place, we need to tackle the score. Specifically, we need to create a piece of state for the current score.

export default function Home({ players, countries }: HomeProps) {
  const [score, setScore] = useState(0);
  ...
}

Once this is done, replace the 0 with {score} in our UI.

You can now check our progress by going to http://localhost:3000. You can see that every time the page refreshes, we get a new name; and when typing in the input field, it lists all of the available unique countries.

Adding some interactivity

We’ve come a decent way but we need to add some interactivity.

Hooking up the guess button

For this we need to have some way of knowing what country was picked. We do this by adding some more state and attaching it to our input field.

export default function Home({ players, countries }: HomeProps) {
  const [score, setScore] = useState(0);
  const [pickedCountry, setPickedCountry] = useState("");
  ...
  return (
    ...
    <input
      list="countries"
      type="text"
      value={pickedCountry}
      onChange={(e) => setPickedCountry(e.target.value)}
      className="p-2 outline-none"
      placeholder="Choose Country"
    />
   ...
  );
}

Next, let’s add a guessCountry function and attach it to the form submission:

const guessCountry = () => {
  if (player.country.toLowerCase() === pickedCountry.toLowerCase()) {
    setScore(score + 1);
  } else {
    alert(‘incorrect’);
  }
};
...
<form
  onSubmit={(e) => {
    e.preventDefault();
    guessCountry();
  }}
>

All we do is basically compare the current player’s country to the guessed country. Now, when we go back to the app and guess the country right, the score increases as expected.

Adding a status indicator

To make this a bit nicer, we can render some UI depending whether the guess is correct or not.

So, let’s create another piece of state for status, and update the guess country method:

const [status, setStatus] = useState(null);
...
const guessCountry = () => {
  if (player.country.toLowerCase() === pickedCountry.toLowerCase()) {
    setStatus({ status: "correct", country: player.country });
    setScore(score + 1);
  } else {
    setStatus({ status: "incorrect", country: player.country });
  }
};

Then render this UI below the player name:

{status && (
  <div className="mt-4 text-lg leading-6 text-white">
    <p>      
      You are {status.status}. It is {status.country}
    </p>
    <p>
      <button
        autoFocus
        className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-blue-600 bg-white hover:bg-blue-50 sm:w-auto"
      >
        Next Player
      </button>
    </p>
  </div>
)}

Lastly, we want to make sure our input field doesn’t show when we are in a correct or incorrect status. We achieve this by wrapping the form with the following:

{!status && (
  <form>
  ...
  </form>
)}

Now, if we go back to the app and guess the player’s country, we get a nice message with the result of the guess.

Progressing through players

Now probably comes the most challenging part: How do we go from one player to the next?

First thing we need to do is store the currentStep in state so that we can update it with a number from 0 to 4. Then, when it hits 5, we want to show a completed state since the trivia game is over.

Once again, let’s add the following state variables:

const [currentStep, setCurrentStep] = useState(0);
const [playersData, setPlayersData] = useState(players);

…then replace our previous player variable with:

const player = playersData[currentStep];

Next, we create a nextStep function and hook it up to the UI:

const nextStep = () => {
  setPickedCountry("");
  setCurrentStep(currentStep + 1);
  setStatus(null);
};
...
<button
  autoFocus
  onClick={nextStep}
  className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-blue-600 bg-white hover:bg-blue-50 sm:w-auto"
 > 
   Next Player
</button>

Now, when we make a guess and hit the next step button, we’re taken to a new tennis player. Guess again and we see the next, and so on. 

What happens when we hit next on the last player? Right now, we get an error. Let’s fix that by adding a conditional that represents that the game has been completed. This happens when the player variable is undefined.

{player ? (
  <div>
    <p className="mt-4 text-lg leading-6 text-blue-200">
      What country is the following tennis player from?
    </p>
    ...
    <p className="mt-4 text-lg leading-6 text-white">
      <strong>Current score:</strong> {score}
    </p>
  </div>
) : (
  <div>
    <button
      autoFocus
      className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-indigo-600 bg-white hover:bg-indigo-50 sm:w-auto"
      >
      Play Again
    </button>
  </div>
)}

Now we see a nice completed state at the end of the game.

Play again button

We are almost done! For our “Play Again” button we want to reset the state all of the game. We also want to get a new list of players from the server without needing a refresh. We do it like this:

const playAgain = async () => {
  setPickedCountry("");
  setPlayersData([]);
  const response = await fetch(
    process.env.NEXT_PUBLIC_API_URL + "/api/newGame"
  );
  const data = await response.json();
  setPlayersData(data.players);
  setCurrentStep(0);
  setScore(0);
};

<button
  autoFocus
  onClick={playAgain}
  className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-indigo-600 bg-white hover:bg-indigo-50 sm:w-auto"
>
  Play Again
</button>

Notice we are using the environment variable we set up before via the process.env object. We are also updating our playersData by overriding our server state with our client state that we just retrieved.

We haven’t filled out our newGame route yet, but this is easy with Next.js and Netlify serverless functions . We only need to edit the file in pages/api/newGame.ts.

import { NextApiRequest, NextApiResponse } from "next"
import { top100Players } from "../../lib/players";

export default (req: NextApiRequest, res: NextApiResponse) => {
  const randomizedPlayers = top100Players.sort((a, b) => 0.5 - Math.random());
  const top5Players = randomizedPlayers.slice(0, 5);
  res.status(200).json({players: top5Players});
}

This looks much the same as our getServerSideProps because we can reuse our nice helper variables.

If we go back to the app, notice the “Play Again” button works as expected.

Improving focus states

One last thing we can do to improve our user experience is set the focus on the country input field every time the step changes. That’s just a nice touch and convenient for the user. We do this using a ref and a useEffect:

const inputRef = useRef(null);
...
useEffect(() => {
  inputRef?.current?.focus();
}, [currentStep]);

<input
  list="countries"
  type="text"
  value={pickedCountry}
  onChange={(e) => setPickedCountry(e.target.value)}
  ref={inputRef}
  className="p-2 outline-none"
  placeholder="Choose Country"
/>

Now we can navigate much easier just using the Enter key and typing a country.

Deploying to Netlify

You may be wondering how we deploy this thing. Well, using Netlify makes it so simple as it detects a Next.js application out of the box and automatically configures it.

All I did was set up a GitHub repo and connect my GitHub account to my Netlify account. From there, I simply pick a repo to deploy and use all the defaults.

The one thing to note is that you have to add the NEXT_PUBLIC_API_URL environment variable and redeploy for it to take effect.

You can find my final deployed version here.

Also note that you can just hit the “Deploy to Netlify” button on the GitHub repo.

Conclusion

Woohoo, you made it! That was a journey and I hope you learned something about React, Next.js, and Netlify along the way.

I have plans to expand this tennis trivia app to use Supabase in the near future so stay tuned!

If you have any questions/comments feel free to reach out to me on Twitter.


Building a Tennis Trivia App With Next.js and Netlify originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/building-a-tennis-trivia-app-with-next-js-and-netlify/feed/ 2 353066
“Disambiguating Tailwind” https://css-tricks.com/disambiguating-tailwind/ https://css-tricks.com/disambiguating-tailwind/#comments Fri, 20 Aug 2021 19:59:47 +0000 https://css-tricks.com/?p=350186 I appreciated this bit of nuance from a post on Viget’s blog:

There could be a whole article written about the many flavours of Tailwind, but broadly speaking those flavours are:

1. Stock tailwind, ie. no changes to the


“Disambiguating Tailwind” originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I appreciated this bit of nuance from a post on Viget’s blog:

There could be a whole article written about the many flavours of Tailwind, but broadly speaking those flavours are:

1. Stock tailwind, ie. no changes to the configuration,
2. Tailwind that heavily relies on @apply in CSS files but still follows BEM or some other component organization,
3. Tailwind UI, and
4. heavily customizing Tailwind’s configuration and writing custom plugins.

Leo Bauza, “How does Viget CSS?”

The way you use some particular technologies can be super different from how someone else does, to the point they share little resemblance, even if they share the same core.

Bootstrap is similar. You can link up Bootstrap off a CDN, the entire untouched built version of everything it offers. You can download the Sass/JavaScript source files, include them in your own project, and bring-your-own build process. This gives you the ability to customize them, but then that complicates the upgrade path. Or you could use Bootstrap from a package manager, meaning you’re referencing the source files from your own build process, but never touching them directly. Either way, if you’re using the source, you can then do things like customize it (change colors, fonts, etc.), and even trim down what parts of it you want to use.

React is similar. Vue is similar. You can link them up right off a CDN and use them right in the browser with no build process. Or they can be at the heart of your build process, and pulled from npm. Or they can be the foundation of a framework like Next or Nuxt.

When you multiply the fact that any given single technology can be used so many different ways with how many different technologies are in use on any given project, it’s no wonder why developer’s experience on projects is so wildly different and you hear a lot of people talking past each other in debate.


“Disambiguating Tailwind” originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/disambiguating-tailwind/feed/ 4 350186
If we’re gonna criticize utility-class frameworks, let’s be fair about it https://css-tricks.com/if-were-gonna-criticize-utility-class-frameworks-lets-be-fair-about-it/ https://css-tricks.com/if-were-gonna-criticize-utility-class-frameworks-lets-be-fair-about-it/#comments Tue, 22 Jun 2021 14:33:41 +0000 https://css-tricks.com/?p=342925 I’m not here to raise a shield protecting CSS utility frameworks. I don’t even particularly like the approach, myself, and nothing is above fair criticism. But fair is a key word there. I can’t tell you how many times I’ve …


If we’re gonna criticize utility-class frameworks, let’s be fair about it originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I’m not here to raise a shield protecting CSS utility frameworks. I don’t even particularly like the approach, myself, and nothing is above fair criticism. But fair is a key word there. I can’t tell you how many times I’ve seen utility styles compared to inline styles. Sarah Dayan is weary of it:

[…] despite numerous attempts at debunking common fallacies, utility-first enthusiasts keep on having to reply to a staggering amount of misconceptions. And by far, the most tired, overused cliché is that utility classes are just inline styles.

I think this comparison will make it clear:

<div style="color: #3ea8ca;"></div>

<div class="color-blue"></div>

The first div has a color set directly in the HTML that is an extremely specific blue color value. The second has a color that is set outside of the HTML, using class name you can use to configure the shade of blue in CSS. Sure, that second one is a fairly limited class name in that, as the name suggests, does one job, but it still offers some abstraction in that the blue color can be changed without changing the markup. It’s the same story with a sizing utility class, say size-xl. That’s also an abstraction we could use to define the padding of an element in CSS using that class name as a selector. But if we were to use style="padding: 10px;" directly on the element in the HTML, that is an absolute that requires changing the value in the markup.

To be fair though (which is what we’re after), there are quite a few classes in utility frameworks that are named in such a way that they are extremely close acting like inline styles. For example, top-0 in Tailwind means top: 0 and there is no configuration or abstraction about it. It’s not like that class will be updated in the CSS with any value other than zero because it’s in the name. “Utility” is a good way to describe that. It is very much like an inline style.

All that configurable-with-smart-defaults stuff puts utility-based frameworks in a different category. Inline styles offer no constraints on how you style things (other than hard limitations, like no pseudo selectors or media queries), while a limited set of utility classes offer quite a lot of styling constraints. Those constraints are often desirable in that they lead to a design that looks consistent and nice instead of inconsistent and sloppy.

To borrow a metaphor I heard in a slightly different context one time: Utility-class frameworks are like bumper bowling for styling. Use the classes and it’ll work out fine. You might not get a strike, but you won’t get a gutter ball either.

Another unfair criticism I hear in conversation about utility frameworks is that you ship way more CSS with them. If you are, then you’re definitely screwing up. In my mind, the main point of this approach is shipping less CSS (only the classes you use). I’m the first to tell you that a build process that accurately and perfectly does this is tricky and could lead to an unhealthy amount of technical debt, but I’ll cede that if you do it right, shipping less CSS is good for performance. Tailwind in particular highly encourages and helps you do this.

So all that said, I think there is all sorts of stuff to criticize about the approach. For example, I personally don’t like looking at all those classes. I just don’t. I’m not an absolutist about perfectly abstract classes, but seeing 10-20 classes on div after div gets in the way of what I’m trying to do when I’m templating HTML. It feels harder to refactor. It feels harder to see what’s going on semantically. It’s harder to parse that list for other classes that I need to do non-styling things. Some of the advantages that I would get from utilities, like scoping styles to exactly where I need them, I often get through other tooling.

I also think utility-frameworks work best in JavaScript component setups where you have Hot Module Reloading. Otherwise, HTML changes tend to trigger entire page refreshes. For example, a tool like Browsersync is pretty darn nice. It does CSS injection when your CSS changes. But it can’t do new-HTML injection; it just refreshes the page. So without Hot Module Reloading, which generally ain’t for your generic HTML site or Static Site Generator, you get worse DX while authoring.


If we’re gonna criticize utility-class frameworks, let’s be fair about it originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/if-were-gonna-criticize-utility-class-frameworks-lets-be-fair-about-it/feed/ 27 342925
How to Use Tailwind on a Svelte Site https://css-tricks.com/how-to-use-tailwind-on-a-svelte-site/ https://css-tricks.com/how-to-use-tailwind-on-a-svelte-site/#comments Fri, 12 Mar 2021 18:53:15 +0000 https://css-tricks.com/?p=335636 Let’s spin up a basic Svelte site and integrate Tailwind into it for styling. One advantage of working with Tailwind is that there isn’t any context switching going back and forth between HTML and CSS, since you’re applying styles as …


How to Use Tailwind on a Svelte Site originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Let’s spin up a basic Svelte site and integrate Tailwind into it for styling. One advantage of working with Tailwind is that there isn’t any context switching going back and forth between HTML and CSS, since you’re applying styles as classes right on the HTML. It’s all the in same file in Svelte anyway, but still, this way you don’t even need a <style> section in your .svelte files.

If you are a Svelte developer or enthusiast, and you’d like to use Tailwind CSS in your Svelte app, this article looks at the easiest, most-straightforward way to install tailwind in your app and hit the ground running in creating a unique, modern UI for your app.

If you like to just see a working example, here’s a working GitHub repo.

Why Svelte?

Performance-wise, Svelte is widely considered to be one of the top JavaScript frameworks on the market right now. Created by Rich Harris in 2016, it has been growing rapidly and becoming popular in the developer community. This is mainly because, while very similar to React (and Vue), Svelte is much faster. When you create an app with React, the final code at build time is a mixture of React and vanilla JavaScript. But browsers only understand vanilla JavaScript. So when a user loads your app in a browser (at runtime), the browser has to download React’s library to help generate the app’s UI. This slows down the process of loading the app significantly.

How’s Svelte different? It comes with a compiler that compiles all your app code into vanilla JavaScript at build time. No Svelte code makes it into the final bundle. In this instance, when a user loads your app, their browser downloads only vanilla JavaScript files, which are lighter. No framework UI library is needed. This significantly speeds up the process of loading your app. For this reason, Svelte applications are usually very small and lightning fast.

The only downside Svelte currently faces is that since it’s still new and doesn’t have the kind of ecosystem and community backing that more established frameworks like React enjoy.

Why Tailwind?

Tailwind CSS is a CSS framework. It’s somewhat similar to popular frameworks, like Bootstrap and Materialize, in that you apply classes to elements and it styles them. But it is also atomic CSS in that one class name does one thing. While Tailwind does have Tailwind UI for pre-built componentry, generally you customize Tailwind to look how you want it to look, so there is less risk of “looking like a Bootstrap site” (or whatever other framework that is less commonly customized).

For example, rather than give you a generic header component that comes with some default font sizes, margins, paddings, and other styling, Tailwind provides you with utility classes for different font sizes, margins, and paddings. You can pick the specific ones you want and create a unique looking header with them.

Tailwind has other advantages as well:

  • It saves you the time and stress of writing custom CSS yourself. With Tailwind, you get thousands of out-of-the-box CSS classes that you just need to apply to your HTML elements.
  • One thing most users of Tailwind appreciate is the naming convention of the utility classes. The names are simple and they do a good job of telling you what their functions are. For example, text-sm gives your text a small font size**.** This is a breath of fresh air for people that struggle with naming custom CSS classes.
  • By utilizing a mobile-first approach, responsiveness is at the heart of Tailwind’s design. Making use of the sm, md, and lg prefixes to specify breakpoints, you can control the way styles are rendered across different screen sizes. For example, if you use the md prefix on a style, that style will only be applied to medium-sized screens and larger. Small screens will not be affected.
  • It prioritizes making your application lightweight by making PurgeCSS easy to set up in your app. PurgeCSS is a tool that runs through your application and optimizes it by removing all unused CSS classes, significantly reducing the size of your style file. We’ll use PurgeCSS in our practice project.

All this said Tailwind might not be your cup of tea. Some people believe that adding lots of CSS classes to your HTML elements makes your HTML code difficult to read. Some developers even think it’s bad practice and makes your code ugly. It’s worth noting that this problem can easily be solved by abstracting many classes into one using the @apply directive, and applying that one class to your HTML, instead of the many.

Tailwind might also not be for you if you are someone who prefers ready-made components to avoid stress and save time, or you are working on a project with a short deadline.

Step 1: Scaffold a new Svelte site

Svelte provides us with a starter template we can use. You can get it by either cloning the Svelte GitHub repo, or by using degit. Using degit provides us with certain advantages, like helping us make a copy of the starter template repository without downloading its entire Git history (unlike git clone). This makes the process faster. Note that degit requires Node 8 and above.

Run the following command to clone the starter app template with degit:

npx degit sveltejs/template project-name

Navigate into the directory of the starter project so we can start making changes to it:

cd project-name

The template is mostly empty right now, so we’ll need to install some required npm packages:

npm install

Now that you have your Svelte app ready, you can proceed to combining it with Tailwind CSS to create a fast, light, unique web app.

Step 2: Adding Tailwind CSS

Let’s proceed to adding Tailwind CSS to our Svelte app, along with some dev dependencies that will help with its setup.

npm install tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

 # or

yarn add tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

The three tools we are downloading with the command above:

  1. Tailwind
  2. PostCSS
  3. Autoprefixer

PostCSS is a tool that uses JavaScript to transform and improve CSS. It comes with a bunch of plugins that perform different functions like polyfilling future CSS features, highlighting errors in your CSS code, controlling the scope of CSS class names, etc.

Autoprefixer is a PostCSS plugin that goes through your code adding vendor prefixes to your CSS rules (Tailwind does not do this automatically), using caniuse as reference. While browsers are choosing to not use prefixing on CSS properties the way they had in years past, some older browsers still rely on them. Autoprefixer helps with that backwards compatibility, while also supporting future compatibility for browsers that might apply a prefix to a property prior to it becoming a standard.

For now, Svelte works with an older version of PostCSS. Its latest version, PostCSS 8, was released September 2020. So, to avoid getting any version-related errors, our command above specifies PostCSS 7 instead of 8. A PostCSS 7 compatibility build of Tailwind is made available under the compat channel on npm.

Step 3: Configuring Tailwind

Now that we have Tailwind installed, let’s create the configuration file needed and do the necessary setup. In the root directory of your project, run this to create a tailwind.config.js file:

npx tailwindcss init  tailwind.config.js

Being a highly customizable framework, Tailwind allows us to easily override its default configurations with custom configurations inside this tailwind.config.js file. This is where we can easily customize things like spacing, colors, fonts, etc.

The tailwind.config.js file is provided to prevent ‘fighting the framework’ which is common with other CSS libraries. Rather than struggling to reverse the effect of certain classes, you come here and specify what you want. It’s in this file that we also define the PostCSS plugins used in the project.

The file comes with some default code. Open it in your text editor and add this compatibility code to it:

future: {
  purgeLayersByDefault: true,
  removeDeprecatedGapUtilities: true,
},

Tailwind 2.0 (the latest version), all layers (e.g., base, components, and utilities) are purged by default. In previous versions, however, just the utilities layer is purged. We can manually configure Tailwind to purge all layers by setting the purgeLayersByDefault flag to true.

Tailwind 2.0 also removes some gap utilities, replacing them with new ones. We can manually remove them from our code by setting removeDeprecatedGapUtilities to true.

These will help you handle deprecations and breaking changes from future updates.

PurgeCSS

The several thousand utility classes that come with Tailwind are added to your project by default. So, even if you don’t use a single Tailwind class in your HTML, your project still carries the entire library, making it rather bulky. We’ll want our files to be as small as possible in production, so we can use purge to remove all of the unused utility classes from our project before pushing the code to production.

Since this is mainly a production problem, we specify that purge should only be enabled in production.

purge: {
  content: [
    "./src/**/*.svelte",
  ],
  enabled: production // disable purge in dev
},

Now, your tailwind.config.js should look like this:

const production = !process.env.ROLLUP_WATCH;
module.exports = {
  future: {
    purgeLayersByDefault: true,
    removeDeprecatedGapUtilities: true,
  },
  plugins: [

  ],
  purge: {
    content: [
     "./src/**/*.svelte",

    ],
    enabled: production // disable purge in dev
  },
};

Rollup.js

Our Svelte app uses Rollup.js, a JavaScript module bundler made by Rich Harris, the creator of Svelte, that is used for compiling multiple source files into one single bundle (similar to webpack). In our app, Rollup performs its function inside a configuration file called rollup.config.js.

With Rollup, We can freely break our project up into small, individual files to make development easier. Rollup also helps to lint, prettify, and syntax-check our source code during bundling.

Step 4: Making Tailwind compatible with Svelte

Navigate to rollup.config.js and import the sveltePreprocess package. This package helps us handle all the CSS processing required with PostCSS and Tailwind.

import sveltePreprocess from "svelte-preprocess";

Under plugins, add sveltePreprocess and require Tailwind and Autoprefixer, as Autoprefixer will be processing the CSS generated by these tools.

preprocess: sveltePreprocess({
  sourceMap: !production,
  postcss: {
    plugins: [
     require("tailwindcss"), 
     require("autoprefixer"),
    ],
  },
}),

Since PostCSS is an external tool with a syntax that’s different from Svelte’s framework, we need a preprocessor to process it and make it compatible with our Svelte code. That’s where the sveltePreprocess package comes in. It provides support for PostCSS and its plugins. We specify to the sveltePreprocess package that we are going to require two external plugins from PostCSS, Tailwind and Autoprefixer. sveltePreprocess runs the foreign code from these two plugins through Babel and converts them to code supported by the Svelte compiler (ES6+). Rollup eventually bundles all of the code together.

The next step is to inject Tailwind’s styles into our app using the @tailwind directive. You can think of @tailwind loosely as a function that helps import and access the files containing Tailwind’s styles. We need to import three sets of styles.

The first set of styles is @tailwind base. This injects Tailwind’s base styles—mostly pulled straight from Normalize.css—into our CSS. Think of the styles you commonly see at the top of stylesheets. Tailwind calls these Preflight styles. They are provided to help solve cross-browser inconsistencies. In other words, they remove all the styles that come with different browsers, ensuring that only the styles you employ are rendered. Preflight helps remove default margins, make headings and lists unstyled by default, and a host of other things. Here’s a complete reference of all the Preflight styles.

The second set of styles is @tailwind components. While Tailwind is a utility-first library created to prevent generic designs, it’s almost impossible to not reuse some designs (or components) when working on a large project. Think about it. The fact that you want a unique-looking website doesn’t mean that all the buttons on a page should be designed differently from each other. You’ll likely use a button style throughout the app.

Follow this thought process. We avoid frameworks, like Bootstrap, to prevent using the same kind of button that everyone else uses. Instead, we use Tailwind to create our own unique button. Great! But we might want to use this nice-looking button we just created on different pages. In this case, it should become a component. Same goes for forms, cards, badges etc.

All the components you create will eventually be injected into the position that @tailwind components occupies. Unlike other frameworks, Tailwind doesn’t come with lots of predefined components, but there are a few. If you aren’t creating components and plan to only use the utility styles, then there’s no need to add this directive.

And, lastly, there’s @tailwind utilities. Tailwind’s utility classes are injected here, along with the ones you create.

Step 5: Injecting Tailwind Styles into Your Site

It’s best to inject all of the above into a high-level component so they’re accessible on every page. You can inject them in the App.svelte file:

<style global lang="postcss">
  @tailwind base;
  @tailwind components;
  @tailwind utilities;
</style>

Now that we have Tailwind set up in, let’s create a website header to see how tailwind works with Svelte. We’ll create it in App.svelte, inside the main tag.

This is what we have so far in the browser. Tailwind gives us everything we need to customize this into something unique, like the header we’re about to create.

Step 6: Creating A Website Header

Starting with some basic markup:

<nav>
  <div>
    <div>
      <a href="#">APP LOGO</a>

      <!-- Menus -->
      <div>
        <ul>
          <li>
            <a href="#">About</a>
          </li>
          <li>
            <a href="#">Services</a>
          </li>
          <li>
            <a href="#">Blog</a>
          </li>
          <li>
            <a href="#">Contact</a>
          </li>
        </ul>
      </div>

    </div>
  </div>
</nav>

This is the header HTML without any Tailwind CSS styling. Pretty standard stuff. We’ll wind up moving the “APP LOGO” to the left side, and the four navigation links on the right side of it.

What we have with zero styling whatsoever.

Now let’s add some Tailwind CSS to it:

<nav class="bg-blue-900 shadow-lg">
  <div class="container mx-auto">
    <div class="sm:flex">
      <a href="#" class="text-white text-3xl font-bold p-3">APP LOGO</a>
      
      <!-- Menus -->
      <div class="ml-55 mt-4">
        <ul class="text-white sm:self-center text-xl">
          <li class="sm:inline-block">
            <a href="#" class="p-3 hover:text-red-900">About</a>
          </li>
          <li class="sm:inline-block">
            <a href="#" class="p-3 hover:text-red-900">Services</a>
          </li>
          <li class="sm:inline-block">
            <a href="#" class="p-3 hover:text-red-900">Blog</a>
          </li>
          <li class="sm:inline-block">
            <a href="#" class="p-3 hover:text-red-900">Contact</a>
          </li>
        </ul>
      </div>

    </div>
  </div>
</nav>

OK, let’s break down all those classes we just added to the HTML. First, let’s look at the <nav> element:

<nav class="bg-blue-900 shadow-lg">

We apply the class bg-blue-900 gives our header a blue background with a shade of 900, which is dark. The class shadow-lg class applies a large outer box shadow. The shadow effect this class creates will be 0px at the top, 10px on the right, 15px at the bottom, and -3px on the left.

Next is the first div, our container for the logo and navigation links:

<div class="container mx-auto">

To center it and our navigation links, we use the mx-auto class. It’s equivalent to margin: auto, horizontally centering an element within its container.

Onto the next div:

<div class="sm:flex">

By default, a div is a block-level element. We use the sm:flex class to make our header a block-level flex container, so as to make its children responsive (to enable them shrink and expand easily). We use the sm prefix to ensure that the style is applied to all screen sizes (small and above).

Alright, the logo:

<a href="#" class="text-white text-3xl font-bold p-3">APP LOGO</a>

The text-white class, true to its name, make the text of the logo white. The text-3xl class sets the font size of our logo (which is configured to 1.875rem)and its line height (configured to 2.25rem). From there, p-3 sets a padding of 0.75rem on all sides of the logo.

That takes us to:

<div class="ml-55 mt-4">

We’re giving the navigation links a left margin of 55% to move them to the right. However, there’s no Tailwind class for this, so we’ve created a custom style called ml-55, a name that’s totally made up but stands for “margin-left 55%.”

It’s one thing to name a custom class. We also have to add it to our style tags:

.ml-55 {
  margin-left: 55%;
}

There’s one more class in there: mt-4. Can you guess what it does? If you guessed that it seta a top margin, then you are correct! In this case, it’s configured to 1rem for our navigation links.

Next up, the navigation links are wrapped in an unordered list tag that contains a few classes:

<ul class="text-white sm:self-center text-xl">

We’re using the text-white class again, followed by sm:self-center to center the list—again, we use the sm prefix to ensure that the style is applied to all screen sizes (small and above). Then there’s text-xl which is the extra-large configured font size.

For each list item:

<li class="sm:inline-block">

The sm:inline-block class sets each list item as an inline block-level element, bringing them side-by-side.

And, lastly, the link inside each list item:

<a href="#" class="p-3 hover:text-red-900">

We use the utility class hover:text-red-900 to make each red on hover.

Let’s run our app in the command line:

npm run dev 

This is what we should get:

And that is how we used Tailwind CSS with Svelte in six little steps!

Conclusion

My hope is that you now know how to integrate Tailwind CSS into our Svelte app and configure it. We covered some pretty basic styling, but there’s always more to learn! Here’s an idea: Try improving the project we worked on by adding a sign-up form and a footer to the page. Tailwind provides comprehensive documentation on all its utility classes. Go through it and familiarize yourself with the classes.

Do you learn better with video? Here are a couple of excellent videos that also go into the process of integrating Tailwind CSS with Svelte.


How to Use Tailwind on a Svelte Site originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/how-to-use-tailwind-on-a-svelte-site/feed/ 23 335636
The Things I Add to Tailwind CSS Right Out of the Box https://css-tricks.com/custom-tailwind-css/ https://css-tricks.com/custom-tailwind-css/#comments Fri, 26 Feb 2021 15:38:36 +0000 https://css-tricks.com/?p=335049 In every project where I use Tailwind CSS, I end up adding something to it. Some of these things I add in every single project. I’ll share these with you, but I’m also curious what y’all are adding to …


The Things I Add to Tailwind CSS Right Out of the Box originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
In every project where I use Tailwind CSS, I end up adding something to it. Some of these things I add in every single project. I’ll share these with you, but I’m also curious what y’all are adding to your tailwind.css files.

I’ll start with myself. In each project:

  • I define -webkit-tap-highlight-color.
  • I add a bottom padding set to env(safe-area-inset-bottom).
  • I dress up unordered lists with interpuncts.

Allow me to elaborate on all three.

-webkit-tap-highlight-color

Android highlights taps on links. I’m not a fan because it obscures the element, so I turn it off for a nicer experience.

@layer base {
  html {
    -webkit-tap-highlight-color: transparent;
  }
}

@layer is a Tailwind directive. It helps avoid specificity issues by telling Tailwind which “bucket” contains a set of custom styles. It’s like pretending the cascade doesn’t exist, so there’s less to worry about when it comes to ordering CSS.

Simply removing the tap highlight color might trigger an accessibility issue since that hides an interactive cue. So, if you go this route, it’s probably a good idea (and I’m still looking for research on this if you have it) to enable :active to define provide some response to those actions. Chris has a snippet for that.

env(safe-area-inset-bottom)

This utility class handles the bottom bar on newer iPhones without the “Home” button. Without it, some elements can fall under the bar, making them unreadable and tough to tap.

@layer utilities {
  .pb-safe {
    padding-bottom: env(safe-area-inset-bottom);
  }
}

Interpuncts

I love using interpuncts with unordered lists. I won’t penalize you for looking that up. We’re basically talking about the bullet points in unordered lists. Tailwind removes them by default via Normalize. I smuggle interpuncts into each and every one of my projects.

Here’s how I go about it:

@layer utilities {
  .list-interpunct > li:before {
    content: '・';
    float: left;
    margin: 0 0 0 -0.9em;
    width: 0.9em;
  }

  @media (min-width: 992px) {
   .list-interpunct > li:before {
      margin: 0 0 0 -1.5em;
      width: 1.5em;
    }
  }
}

We also now have ::marker to do the same thing and it’s a little easier to work with. I’m not using it, because of the limited support in Safari.

Now it’s your turn

Alright, I shared what I add to all of my Tailwind projects, so now it’s your turn. What do you add to Tailwind in your projects? Is there something you can’t do without? Let me know in the comments! I’d love ideas to start incorporating into other projects.


The Things I Add to Tailwind CSS Right Out of the Box originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/custom-tailwind-css/feed/ 13 335049
Why I love Tailwind https://css-tricks.com/why-i-love-tailwind/ https://css-tricks.com/why-i-love-tailwind/#comments Fri, 11 Dec 2020 15:52:12 +0000 https://css-tricks.com/?p=330717 Max Stoiber wrote some interesting notes about why he loves Tailwind. (Max created styled-components, so he has some skin in the styling methodology game.) There’s a lot of great history in this post about how Tailwind emerged and …


Why I love Tailwind originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Max Stoiber wrote some interesting notes about why he loves Tailwind. (Max created styled-components, so he has some skin in the styling methodology game.) There’s a lot of great history in this post about how Tailwind emerged and became a valuable tool for designers and engineers alike, but he also talks about what beats at the very heart of the Tailwind system and what makes it just so handy:

The key to Tailwind’s popularity is the painstakingly constructed system of design tokens at the core of the framework. The system’s carefully selected constraints give developers just the right guardrails. They make it obvious whether a choice is good or bad by offering only discrete steps.

He links to twin.macro — something I’d never heard of before — then gives an example that looks something like this:

import "twin.macro"

<div tw="text-center md:text-left" />

// ↓↓↓↓↓ turns into ↓↓↓↓↓

import "styled-components/macro"

<div 
  css={{
    textAlign: "center",
    "@media (min-width: 768px)": {
      "textAlign":"left"
    }
  }}
/>

What’s happening here is that you can use predefined classes just like you would with Tailwind — add spacing, make a div round, and a certain size, etc. What twin.macro does is let you use these classes, but with the additional benefits of CSS-in-JS. Max writes:

You get fully automatic critical CSS extraction and code splitting. Users will only load exactly the styles they need for the page they requested — nothing more and nothing less!

I sort of love this, using Tailwind as a shorthand, treating it more like a syntactic sugar on top of CSS rather than as a framework. Super interesting stuff.

To Shared LinkPermalink on CSS-Tricks


Why I love Tailwind originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/why-i-love-tailwind/feed/ 3 330717
Tailwind versus BEM https://css-tricks.com/tailwind-versus-bem/ https://css-tricks.com/tailwind-versus-bem/#comments Wed, 25 Nov 2020 21:40:03 +0000 https://css-tricks.com/?p=326169 Some really refreshing technological comparison writing from Eric Bailey. Like, ya know, everything in life, we don’t have to hate or love everything. Baby bear thinking, I like to say. There are benefits and drawbacks. Every single bullet point here …


Tailwind versus BEM originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Some really refreshing technological comparison writing from Eric Bailey. Like, ya know, everything in life, we don’t have to hate or love everything. Baby bear thinking, I like to say. There are benefits and drawbacks. Every single bullet point here is well-considered and valid. I really like the first in each section, so I’ll quote those as a taste here:

Tailwind Benefit: “The utility CSS approach creates an API-style approach to thinking about CSS, which helps many developers work with it.”

Tailwind Drawback: “You need to learn Tailwind class names in addition to learning CSS property names to figure out the visual styling you want. Tailwind is reliant on, and will be outlived by CSS, so it is more long-term beneficial to focus on CSS’ capabilities directly.”

BEM Benefit: “BEM will allow you to describe any user interface component you can dream up in a flexible, extensible way. As it is an approach to encapsulate the full range of CSS properties, it will allow you to style things Tailwind simply does not have classes for—think highly art directed experiences.”

BEM Drawback: “BEM runs full-tilt into one of the hardest problems in computer science—naming things. You need to not only describe your component, but also all its constituent parts and their states.”


And remember, these certainly aren’t the only two choices on the block. I covered my thoughts on some other approaches here.

To Shared LinkPermalink on CSS-Tricks


Tailwind versus BEM originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/tailwind-versus-bem/feed/ 1 326169
Color Theming with CSS Custom Properties and Tailwind https://css-tricks.com/color-theming-with-css-custom-properties-and-tailwind/ https://css-tricks.com/color-theming-with-css-custom-properties-and-tailwind/#comments Thu, 19 Nov 2020 15:52:59 +0000 https://css-tricks.com/?p=325393 Custom properties not only enable us to make our code more efficient, but allow us to work some real magic with CSS too. One area where they have huge potential is theming. At Atomic Smash we use Tailwind CSS, …


Color Theming with CSS Custom Properties and Tailwind originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Custom properties not only enable us to make our code more efficient, but allow us to work some real magic with CSS too. One area where they have huge potential is theming. At Atomic Smash we use Tailwind CSS, a utility class framework, for writing our styles. In this article, we’ll look at how custom properties can be used for theming, and how we can integrate them with Tailwind to maximize the reusability of our code. We won’t cover getting up and running with Tailwind — check out the official documentation for that — but even if you’re new to it you might find some of these tips useful.

Theming overview

Let’s say we have a “Call To Action” (CTA) component with a heading, body copy, and button.

A box with a light red heading that reads join our mailing list above a dark red body that reads be the first to hear about our new offerings right before a red signup button.

Writing regular (non-Tailwind) CSS for this color scheme would look something like this:

.cta {
  background-color: #742a2a; // dark red
  color: #ffffff; //white
}
    
.cta__heading {
  background-color: #e53e3e; // medium red
  color: #742a2a;
}


.cta__button {
  background-color: #e53e3e;
}

Using Tailwind, we would apply these colors as utility classes in our HTML:

<div class="bg-red-900 text-white">
  <h3 class="bg-red-600 text-red-900">Join our mailing list</h3>
  <div>
    <p>Be the first to hear about our new offerings</p>
    <button class="bg-red-600" type="button">Sign up</button>
  </div>
</div>

I’ve deliberately left out classes relating to anything other than the basic color scheme, but you can see the example in its entirety in this demo:

Now, if we wanted to apply a different color scheme to our component, we would need to override the color values of our original component. Without Tailwind, a common way to do that would be to append a theme class to the component itself, and redefine the color values lower down in the cascade. So for a component with a modifier class of .cta--blue (using the BEM convention) we’ll apply the CSS values for a blue color scheme:

.cta--blue {
  background-color: #2a4365; // dark blue
}


.cta--blue .cta__heading {
  background-color: #3182ce; // medium blue
  color: #2a4365;
}


.cta--blue .cta__button {
  background-color: #3182ce;
}
A box with a light blue heading that reads join our mailing list above a dark bluebody that reads be the first to hear about our new offerings right before a blue signup button.

If we’re using Sass or another preprocessor, it’s likely we’ll make life easier for ourselves by using variables for those color names, and we might nest the .cta__heading and .cta__body selectors. It doesn’t exactly make our code more concise, but it does make it more manageable by having a single place to update those values.

Now, suppose we have 10 different color schemes, as was my experience on a recent project. Our code starts to get longer, as we’re basically duplicating the above example 10 times in order to change those color values. Now imagine every component in our design system needs 10 color schemes, and many of those components are far more complex than our simple CTA. Maybe our themes need different fonts too. Suddenly we have a lot of CSS to write.

Theming with Tailwind

If we’re using Tailwind, on the other hand, we’d need to change multiple classes in the HTML itself. Even if we’re using a JavaScript framework, like React or Vue, this is not exactly a trivial task. In order to ensure unused styles are removed in a production build, Tailwind discourages the use of string concatenation for class names (at the time of writing). So building our themes means potentially piling a lot of logic into our components.

Theming with Custom Properties

By using custom properties for our color themes, we can drastically reduce the amount of code we need to write, and alleviate the maintenance burden. Let’s first take a look at how we can do this in regular CSS.

We define our custom properties as variables on the :root selector, making them global variables. (The body selector would serve us just as well.) Then we can use those variables in a selector, in place of our color property values:

:root {
  --primary: #742a2a; // dark red;
  --secondary: #e53e3e; // medium red
}


.cta {
  background-color: var(--primary);
  color: white;
}


.cta__heading {
  background-color: var(--secondary);
  color: var(--primary);
}


.cta__button {
  background-color: var(--secondary);
}

This is where the real magic happens: now the code for creating each of our themes becomes a case of only updating those custom property values. The new values will be inherited wherever we apply our theme class:

.th-blue {
  --primary: #2a4365; // dark blue
  --secondary: #3182ce; // medium blue
}

If we want a blue color scheme, we can apply that .th-blue class to the component, or even use it on the <body> tag to apply to apply a page-wide theme, which can be overridden on individual components as desired. Using a utility class potentially saves us writing even more code compared to a component-specific class (such as .cta--blue in the original code), as it could be applied anywhere in our codebase.

Handling older browsers

Like many agencies, plenty of our clients at Atomic Smash still require us to support Internet Explorer 11. While I’m okay with a progressive enhancement approach in most cases (by providing simpler fallback layouts for browsers that don’t support CSS Grid, for instance), I find theming is one area that often doesn’t allow for easy compromise. Clients want their brand colors and fonts seen, even on older browsers. Providing fallbacks using feature queries would entail a lot of extra work that would negate the benefits of using custom properties in the first place. To overcome this, we need a polyfill.

There are a couple of options for polyfilling custom properties in IE 11.

postcss-custom-properties

The first is using a PostCSS plugin called postcss-custom-properties. If you’re already using PostCSS in your workflow, this is fairly simple to add. It works by processing your CSS and outputting the result of the variable as the property value. So if you have the following CSS:

:root {
  --color: red;
}


h1 {
  color: var(--color);
}

The processed result will be:

h1 {
  color: red;
  color: var(--color);
}

Browsers that don’t support custom properties will ignore the second rule and fall back to the regular property value. There is also an option to remove the rules with the custom properties in the output, so the file size will be smaller. This means that no browsers will get the custom property — which is an issue if you’re updating variables dynamically — but you’ll be able to use them for static values in your code with no ill effects.

Unfortunately this polyfill has some limitations:

  1. You need to specify the file (or files) in your config where you’re defining the custom properties.
  2. Custom properties can only be defined on the :root selector.

The first limitation is relatively trivial, but the second unfortunately renders this polyfill entirely useless for our theming use case. It means we can’t redefine variables on a selector to create our themes.

ie11CustomProperties

This polyfill option involves serving a client-side script, rather than preprocessing the CSS. We can add the following script to our head to ensure the polyfill will only be loaded in IE 11:

<script>window.MSInputMethodContext && document.documentMode && document.write('<script src="https://cdn.jsdelivr.net/gh/nuxodin/ie11CustomProperties@4.1.0/ie11CustomProperties.min.js"><\/script>');</script>

This permits us to enjoy the full benefits of custom properties as in the examples here, so it’s the solution I decided to go with. It has a limitation where custom properties set in style attributes aren’t polyfilled. But I’ve tested it for the theming example above and it works just fine.

But what does this have to do with Tailwind?

As we’ve already seen, utility classes — single-purpose classes that can be applied anywhere in our HTML — can make our code more reusable. That’s the main selling point of Tailwind and other utility class frameworks — the size of the CSS file you ship should end up smaller as a result. Tailwind makes multiple color classes available: .bg-red-medium would give us a red background-color property value, .text-red-medium for color and so on for border, box-shadow, or any place you can think of that you might need a color value. 

Colors can be defined in a config file:

module.exports = {
  theme: {
    colors: {
      red: {
        medium: '#e53e3e',
        dark: '#742a2a'
      },
      blue: {
        medium: '#3182ce',
        dark: '#2a4365'
      }
    }
  }
}

If we want to use custom property values for our Tailwind classes, we can specify them in the config:

module.exports = {
  theme: {
    colors: {
      'th-primary': 'var(--primary)',
      'th-secondary': 'var(--secondary)'
    }
  }
}

I’m prefixing my colors and theme-related class names with th- so that it’s obvious they’re specifically related to theming, but feel free to use whatever convention suits you.

Now those classes will be available to us through Tailwind. Using .bg-th-primary gives us the equivalent of writing:

.some-element {
  background-color: var(--primary);
}

In our CSS we can define our custom properties for our themes as before:

:root {
  --primary: #742a2a;
  --secondary: #742a2a;
}


.th-blue {
  --primary: #2a4365;
  --secondary: #3182ce;
}

Let’s apply those classes to our HTML. The first example gives us a component with our default theme (the variables defined on the :root). The second has our blue theme. The only difference is the addition of the .th-blue class on the component. (Once again, I’ve omitted the classes unrelated to the theme, for brevity and clarity.)

<!--Component with default (red) theme-->
<div class="bg-th-primary">
  <h3 class="bg-th-secondary text-th-primary">Join our mailing list</h3>
  <div>
    <p>Be the first to hear about our new offerings</p>
    <button class="bg-th-secondary" type="button">Sign up</button>
  </div>
</div>


<!--Component with blue theme-->
<div class="th-blue bg-th-primary">
  <h3 class="bg-th-secondary text-th-primary">Join our mailing list</h3>
  <div>
    <p>Be the first to hear about our new offerings</p>
    <button class="bg-th-secondary" type="button">Sign up</button>
  </div>
</div>

Using the config as a style guide

Tailwind encourages you to define all variables in the config, and personally I agree that it’s a better approach. It means that the config file can be a single source of truth rather than (potentially) ending up with multiple places to define your colors and other theme values. Luckily, we can also use values from the Tailwind config file for our custom properties. We’ll need to first define all of our colors in the config (assuming we’re not using the default color palette included with Tailwind):

module.exports = {
  theme: {
    colors: {
      red: {
        medium: '#e53e3e',
        dark: '#742a2a'
      },
      blue: {
        medium: '#3182ce',
        dark: '#2a4365'
      },
      'th-primary': 'var(--primary)',
      'th-secondary': 'var(--secondary)'
    }
  }
}

Then we can access the theme object in the CSS:

:root {
  --primary: theme('colors.red.dark');
  --secondary: theme('colors.red.medium');
}


.th-blue {
  --primary: theme('colors.blue.dark');
  --secondary: theme('colors.blue.medium');
}

Wrapping up

I’m really excited about the benefits of being able to use custom properties without having to worry about browser support, even more so to be able to integrate them smoothly with our existing workflow. It’s hard to overstate the amount of time they will save us for theming. I hope that even if you’re not a Tailwind user, this article might encourage you to give custom properties a go for this use case.


Color Theming with CSS Custom Properties and Tailwind originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/color-theming-with-css-custom-properties-and-tailwind/feed/ 6 325393
An Eleventy Starter with Tailwind CSS and Alpine.js https://css-tricks.com/eleventy-starter-with-tailwind-css-alpine-js/ https://css-tricks.com/eleventy-starter-with-tailwind-css-alpine-js/#comments Fri, 10 Jul 2020 14:54:14 +0000 https://css-tricks.com/?p=316670 When I decided to try to base my current personal website on Eleventy, I didn’t want to reinvent the wheel: I tested all the Eleventy starters built with Tailwind CSS that I could find in Starter Projects from the …


An Eleventy Starter with Tailwind CSS and Alpine.js originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
When I decided to try to base my current personal website on Eleventy, I didn’t want to reinvent the wheel: I tested all the Eleventy starters built with Tailwind CSS that I could find in Starter Projects from the documentation.

Many of the starters seemed to integrate Tailwind CSS in a contrived way. Also, some of them seemed to assume that no one updates Tailwind’s configuration on the fly while working on a website. That’s why I integrated Eleventy with Tailwind CSS and Alpine.js myself. I have reason to believe that you’ll like the simplicity of my solution.

Good design is as little design as possible.

—Dieter Rams, 10 Principles for Good Design

If you’re uninterested in the details, feel free to grab my starter and jump right in.

Getting started

I’m going to assume you have a general understanding of Tailwind CSS, HTML, JavaScript, Nunjucks, the command line, and npm.

Let’s start by with a new a folder, then cd to it in the command line, and initialize it with a package.json file:

npm init -y

Now we can install Eleventy and Tailwind CSS:

npm install -D @11ty/eleventy tailwindcss@latest

We need to create a page to test whether we’ve successfully set things up. In a real use case, our pages will use templates, so we’ll do that here as well. That’s where Nunjucks fits into the mix, serving as a templating engine.

Let’s make a new file called index.njk in the project folder. We’ll designate it as the homepage:

{% extends "_includes/default.njk" %}


{% block title %}It does work{% endblock %}


{% block content %}
  <div class="fixed inset-0 flex justify-center items-center">
    <div>
      <span class="text-change">Good design</span><br/>
      <span class="change">is<br/>as little design<br/>as possible</span>
    </div>
  </div>
{% endblock %}

Basic templating

Now let’s create a new folder in the project folder called _includes (and yes, the folder name matters). Inside this new folder, we’ll create a file called default.njk that we’ll use as the default template for our layout. We’ll keep things simple with a basic HTML boilerplate:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>
      {% block title %}Does it work?{% endblock %}
    </title>
    <meta charset="UTF-8"/>
    {% if description %}
      <meta name="description" content="{{description}}"/>
    {% endif %}
    <meta http-equiv="x-ua-compatible" content="ie=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
    <link rel="stylesheet" href="/style.css?v={% version %}"/>
    {% block head %}{% endblock %}
  </head>
  <body>
    {% block content %}
      {{ content | safe }}
    {% endblock %}
  </body>
</html>

Configuring Tailwind CSS

Let’s take care of a test for Tailwind CSS in as few moves as possible. First, create a new subfolder called styles and a file in it called tailwind.config.js:

module.exports = {
  content: ['_site/**/*.html'],
  safelist: [],
  theme: {
    extend: {
      colors: {
        change: 'transparent',
      },
    },
  },
  plugins: [],
}

Then, create a file called tailwind.css in that same styles folder:

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer utilities {
  .change {
    color: transparent;
  }
}

Starting and building the project

Now let’s create another new file in the same root directory called .gitignore. This will allow us to define what files to skip when committing the project to a repo, like on GitHub:

_site/
.DS_Store
node_modules/

Next, we’ll create a file called .eleventy.js (note the leading dot) that basically configures Eleventy, telling it what files to watch and where to save its work:

const now = String(Date.now())

module.exports = function (eleventyConfig) {
  eleventyConfig.addWatchTarget('./styles/tailwind.config.js')
  eleventyConfig.addWatchTarget('./styles/tailwind.css')


  eleventyConfig.addPassthroughCopy({ './_tmp/style.css': './style.css' })


  eleventyConfig.addShortcode('version', function () {
    return now
  })
};

We can now update the package.json file with all of the scripts we need to start and build the site during development. The dependencies should already be there from the initial setup.

{
  "scripts": {
    "start": "eleventy --serve & npx tailwindcss -i styles/tailwind.css -c styles/tailwind.config.js -o _site/style.css --watch",
    "build": "ELEVENTY_PRODUCTION=true eleventy && NODE_ENV=production npx tailwindcss -i styles/tailwind.css -c styles/tailwind.config.js -o _site/style.css --minify"
  },
  "devDependencies": {
    "@11ty/eleventy": "^1.0.0",
    "tailwindcss": "^3.0.0"
  }
}

Hey, great job! We made it. Let’s officially start the site:

npm start

Open the page http://localhost:8080 in your browser. It’s not gonna look like much, but check out the page title in the browser tab:

It does work!

We can still do a little more checking to make sure everything’s good. Open up /styles/tailwind.config.js and change the transparent color value to something else, say black. Tailwind’s configuration should reload, along with the page in your browser.

Don’t lose sight of your browser and edit /styles/tailwind.css by changing transparent to black again. Your CSS file should reload and refresh in your browser.

Now we can work nicely with Eleventy and Tailwind CSS!

Optimizing the output

At this point, Tailwind CSS works with Eleventy, but the generated HTML isn’t perfect because it contains stuff like redundant newline characters. Let’s clean it up:

npm install -D html-minifier

Add the following line to the beginning of the .eleventy.js file:

const htmlmin = require('html-minifier')

We also need to configure htmlmin in .eleventy.js as well:

eleventyConfig.addTransform('htmlmin', function (content, outputPath) {
    if (
      process.env.ELEVENTY_PRODUCTION &&
      outputPath &&
      outputPath.endsWith('.html')
    ) {
      let minified = htmlmin.minify(content, {
        useShortDoctype: true,
        removeComments: true,
        collapseWhitespace: true,
      });
      return minified
    }


    return content
})

We’re using a transform here which is an Eleventy thing. Transforms can modify a template’s output. At this point, .eleventy.js should look like this:

const htmlmin = require('html-minifier')

const now = String(Date.now())

module.exports = function (eleventyConfig) {
  eleventyConfig.addWatchTarget('./styles/tailwind.config.js')
  eleventyConfig.addWatchTarget('./styles/tailwind.css')

  eleventyConfig.addShortcode('version', function () {
    return now
  })

  eleventyConfig.addTransform('htmlmin', function (content, outputPath) {
    if (
      process.env.ELEVENTY_PRODUCTION &&
      outputPath &&
      outputPath.endsWith('.html')
    ) {
      let minified = htmlmin.minify(content, {
        useShortDoctype: true,
        removeComments: true,
        collapseWhitespace: true,
      })
      return minified
    }

    return content
  })
}

Let’s run npm start once again. You’ll see that nothing has changed and that’s because optimization only happens during build. So, instead, let’s try npm run build and then look at the _site folder. There shouldn’t be a single unnecessary character in the index.html file. The same goes for the style.css file.

A project built like this is now ready to deploy. Good job! 🏆

Integrating Alpine.js

I decided to switch to Eleventy from Gatsby.js because it just felt like too much JavaScript to me. I’m more into the reasonable dose of vanilla JavaScript mixed with Alpine.js. We won’t get into the specifics of Alpine.js here, but it’s worth checking out Hugo DiFrancesco’s primer because it’s a perfect starting point.

Here’s how we can install it to our project from the command line:

npm install -D alpinejs

Now we need to update .eleventy.js with this to the function that passes things through Alpine.js:

eleventyConfig.addPassthroughCopy({
  './node_modules/alpinejs/dist/cdn.js': './js/alpine.js',
})

Lastly, we’ll open up _includes/default.njk and add Alpine.js right before the closing </head> tag:

<script src="/js/alpine.js?v={% version %}"></script>

We can check if Alpine is working by adding this to index.njk:

{% extends "_includes/default.njk" %}


{% block title %}It does work{% endblock %}


{% block content %}
  <div class="fixed inset-0 flex justify-center items-center">
    <div>
      <span class="text-change">Good design</span><br/>
      <span class="change">is<br/>as little design<br/>as possible</span><br/>
      <span x-data="{message:'🤖 Hello World 🤓'}" x-text="message"></span>
    </div>
  </div>
{% endblock %}

Launch the project:

npm start

If Alpine.js works, you’ll see “Hello World” in your browser. Congratulations, times two! 🏆🏆


I hope you can see how quick it can be to set up an Eleventy project, including integrations with Nunjucks for templating, Tailwind for styles and Alpine.js for scripts. I know working with new tech can be overwhelming and even confusing, so feel free to email me at csstricks@gregwolanski.com if you have problems starting up or have an idea for how to simplify this even further.


An Eleventy Starter with Tailwind CSS and Alpine.js originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/eleventy-starter-with-tailwind-css-alpine-js/feed/ 25 316670