ElianCodes

← Back to blog

Configure TailwindCSS with Blazor

I had to make a project for school with Blazor WASM and I wanted to use TailwindCSS with it. A new Blazor project is quickly setup, but it uses Bootstrap out of the box, so how do we configure it to use TailwindCSS?

A little heads up. It’s not that easy, if you plan on using the basics, you could also use the CDN, but if you plan on using more advanced features in the configuration like using Sass and @apply classes or purging, it’s worth it to find a solution.

Starting a new Blazor project

Starting a new blazor project is very easy to do. First, we have to install the dotnet SDK, which can be done easily here. It’s a very straight forward process.

After that, we can bootstrap the default Blazor WASM template by running

dotnet new blazorserver -o yourAmazingApp --no-https

that will create a new directory called yourAmazingApp in the location you ran the command in.

Now we can start up the project in watchmode by running

dotnet watch run

Preparing a package.json

So now we have a basic project, but no Tailwind configuration or even a package.json file. Since Blazor isn’t really meant to do this stuff, we have to make a workaround, but it works fine.

Theoretical solution

We can create a little local project with Yarn and use that to download our node_modules. Then we use Webpack to compile and export our Tailwind.scss file to a main.css file in the wwwroot/css folder.

Adding the package.json file

We create a folder called assets in our root directory of our .csproj. in that folder, we can run npm init or yarn init. For the rest of this post I’ll use Yarn, but feel free to use NPM, it should also work.

so when we initialize a package.json file we can add some dependencies. Below are some I used, but if you’re not using Sass, you can leave out some.

"devDependencies": {
    "postcss-import": "^14.0.0",
    "webpack-fix-style-only-entries": "^0.5.1",
    "autoprefixer": "^10.2.4",
    "css-loader": "^3.2.0",
    "mini-css-extract-plugin": "^0.8.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss": "^8.2.6",
    "postcss-easy-import": "^3.0.0",
    "postcss-loader": "^4.2.0",
    "sass": "^1.32.8",
    "sass-loader": "^10.1.1",
    "style-loader": "^1.0.0",
    "webpack": "^4.41.0",
    "webpack-cli": "^3.3.9",
    "webpack-watch-files-plugin": "^1.0.3"
  },
  "dependencies": {
    "postcss-cli": "^8.3.1",
    "postcss-nested": "^5.0.3",
    "tailwindcss": "^2.0.3"
  },

Now we have our dependencies installed, we can create some configuration files.

Configuration files

postcss.config.js

the postcss.config.js file is used to process tailwind to our custom stylesheet. Here it also imports some other things, but feel free to only add require('tailwindcss').

module.exports = {
  plugins: [
    require("postcss-easy-import")({
      prefix: "_",
      extensions: [".css", ".scss"],
    }),
    require("tailwindcss"),
    require("autoprefixer"),
    require("postcss-nested"),
  ],
};

webpack.config.js

In the webpack.config.js file we basically tell Webpack to take our raw /assets/scss/tailwind.scss file and compile it to a main.css file and add it to the wwwroot/css/ folder

const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const FixStyleOnlyEntriesPlugin = require("webpack-fix-style-only-entries");

module.exports = (env, args) => ({
  devtool: args.mode === "development" ? "source-map" : "none",
  entry: "./scss/tailwind.scss",
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "..", "wwwroot/css"),
  },
  module: {
    rules: [
      {
        test: /\.scss$/,
        exclude: /node_modules/,
        use: [
          MiniCssExtractPlugin.loader,
          { loader: "css-loader", options: { url: false, sourceMap: true } },
          { loader: "postcss-loader" },
          { loader: "sass-loader" },
        ],
      },
    ],
  },
  plugins: [
    new FixStyleOnlyEntriesPlugin(),
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
  ],
});

tailwind.config.js

below is the basic configuration of TailwindCSS without many to it. But from here we can customize it in any way we want like normal.

module.exports = {
  purge: [],
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
};

Configure Blazor to use TailwindCSS

So now we have our compiled main.css file, but it won’t trigger when we build or run our dotnet project. For that, we have to create a separate file called assets.targets which will tell our dotnet project to trigger the package.json build scripts.

<Project>

    <ItemGroup>
        <StaticAssets Include="$(MSBuildThisFileDirectory)**" Exclude="$(MSBuildThisFileDirectory)node_modules\**" />
        <UpToDateCheckInput Include="@(StaticAssets)" />
    </ItemGroup>

    <PropertyGroup>
        <StaticCSSPath>scss\tailwind.scss</StaticCSSPath>
    </PropertyGroup>

    <!-- If lockfile has changed, perform a new yarn install -->
    <Target Name="yarnInstall"
            Inputs="$(MSBuildThisFileDirectory)yarn.lock"
            Outputs="$(BaseIntermediateOutputPath)yarn.lock">
        <Message Importance="high" Text="Restoring dependencies using yarn. This may take several minutes..." />
        <Exec Command="yarn" WorkingDirectory="$(MSBuildThisFileDirectory)" />
        <Copy SourceFiles="$(MSBuildThisFileDirectory)yarn.lock"
              DestinationFolder="$(BaseIntermediateOutputPath)"
              SkipUnchangedFiles="true"/>
    </Target>

    <!-- If any source file in this dir or below has changed, perform a Webpack build -->
    <Target Name="BuildStaticAssets"
            DependsOnTargets="yarnInstall"
            BeforeTargets="CoreBuild"
            Inputs="@(StaticAssets)"
            Outputs="$(MSBuildThisFileDirectory)..\wwwroot\$(StaticCSSPath)">
        <Exec Command="yarn build:$(Configuration)" WorkingDirectory="$(MSBuildThisFileDirectory)" />
    </Target>

</Project>

now add the build scripts in package.json and we’re pretty much done.

"scripts": {
    "build:Debug": "webpack --mode development",
    "build:Release": "webpack --mode production"
  },

from this point on we can use Tailwind in any way we want. First, we have to add it to our project by modifying our .csproj file.

yourAwesomeProject.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">

   // default configuration here ...

  <Import Project="assets\assets.targets" />
</Project>

Run your project

voila! Now every time you run dotnet build or debug your project. The scripts will run or at least checked. If everything goes correct, you should see the main.css file in your /wwwroot/css folder!

Now we can include and use it in our markup

Pages/_Host.cshtml:

<head>
  <meta charset="utf-8" />
  <title>YourAwesomeProject</title>
  <link href="~/css/main.css" rel="stylesheet" />
</head>

Also don’t forget to put that file in your .gitignore file otherwise you’ll push and pull a file that gets generated, which is useless.

Written by Elian Van Cutsem

← Back to blog
  • Elians Astro Startkit

    Elians Astro Startkit

    I just published the setup I use for my personal projects. You can read more about it in this blogpost.

  • So, I'm leaving vBridge

    So, I'm leaving vBridge

    After spending a couple of years at vBridge Cloud, I'm leaving the company. I've worked at vBridge eversince I graduated. Now, It's time for a new adventure! I'm joining the DX-team at Astro full-time!

  • Becoming an Astro maintainer

    Becoming an Astro maintainer

    Since a week, I'm an Astro maintainer, in this post, I describe the process and my start in open source. I also give some insight in what I'm planning to work on.

  • 🍱 Brutal: A theme for Astro

    🍱 Brutal: A theme for Astro

    Brutal is a minimal neobrutalist theme for Astro. It's based on Neobrutalist Web Design, a movement that aims to create websites with a minimalistic and functional design. It has some integrations like Image Optimization, RSS, Sitemap, ready to get your SEO done right.

  • 🎤 Am I an international public speaker now?

    🎤 Am I an international public speaker now?

    A few weeks ago, I gave my first international keynote talk at JSWorld in Amsterdam. In this blogpost, I wanted to share some insights about the conference and my talk.

  • ✨ Building Blog tag index pages in Astro

    ✨ Building Blog tag index pages in Astro

    I wanted to add blog tag collection pages to my website. This way, people could filter on tags I used in my blog posts. Here is a guide on how I implemented it.