Using TailwindCSS with Blazor and scoped CSS

Yannick Haas
medialesson
Published in
4 min readAug 6, 2024

Using scoped CSS with Blazor significantly simplifies naming our classes and writing CSS in general as we don’t have to constantly check, if we already used this class somewhere. If we then put TailwindCSS on top, we further simplify it, by enabling us to use the predefined classes from TailwindCSS in our HTML. However, for more complex selectors like :not(:first-child) the class tag might become almost unreadable as it adds a lot of clutter. This is where the scoped CSS file comes in handy. However, traditional TailwindCSS setups only compile the app.css and ignore all other CSS files. How can we fix this?

TailwindCSS Setup

Let’s start by setting up TailwindCSS in a Blazor (WASM) project.

For ease of use and keeping Tailwind’s files separate from the rest of the project, create a folder called tailwindcss (or anything you like) either directly in the project, which uses TailwindCSS, or in the src folder. Navigate to that folder in your console and execute:

npm i autoprefixer glob postcss postcss-cli postcss-import postcss-scss tailwindcss

Why did I include postcss-scss and postcss-import? I personally prefer using scss and it also makes handling of the “uncompiled” vs “compiled” files easier. That way we can also ensure we don’t serve files with `@apply` in them, which wouldn’t be readable by the browser.

After we’ve got our dependencies installed, we can execute npx tailwindcss init , which will create the tailwindcss.config.js for us.

In the tailwindcss.config.js , we have to adjust which files should be relevant for TailwindCSS. We do this by changing content: [] to:

content: ["../**/*.{razor,html,cshtml,scss}"],

Now we create a file called postcss.config.js with the following content:

module.exports = {
syntax: 'postcss-scss',
plugins: {
'postcss-import': {},
tailwindcss: {},
autoprefixer: {},
}
}

This ensures we can use SCSS, TailwindCSS, and any imported files in our SCSS get compiled into the resulting CSS as well.

Lastly we have to add tailwind to our app.scss file, so we can actually use it:

// These have to be at the very start of the file after any @import statements
@tailwind base;
@tailwind components;
@tailwind utilities;

Compile SCSS to CSS and use with scoped SCSS files

Since we’ve got multiple files we need to compile to CSS, we should set up a script, which gets executed before each build. Create a file called build-css.js and paste the following content:

const {globSync} = require('glob');
const {exec, spawn} = require('child_process');

const watch = process.argv.slice(2).includes("--watch");

const srcPath = __dirname.slice(0, __dirname.indexOf("src") + 3);

console.log("Building CSS files...");
const scssFiles = globSync(`${srcPath}/**/*.scss`);
scssFiles.forEach((file) => {
const command = `npx postcss "${file}" -o "${file.replace('.scss', '.css')}" --config "${__dirname}/postcss.config.js" ${watch ? "--watch --verbose" : ""}`;

if(watch) {
spawn(command, {shell:true, stdio: 'inherit'});
} else {
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
}
});
}
});

console.log("Done building CSS files");

This script assumes our projects are located inside a src folder and then compiles all SCSS files in all projects. You can of course also run it for the project the tailwindcss folder is in, by replacing const srcPath = … with const srcPath = '..' . This file also contains the “magic” allowing us to have scoped CSS files as it looks for all files instead of only the app.(s)css .

By default Blazor uses CSS files and dotnet will always create CSS files instead of SCSS files. So each time we create a new component with scoped CSS, we have to rename the file from *.css to *.scss . We also have to do this for all currently existing files.

Building and making life easier using scripts

To make it easier to run the SCSS-build, we add the following scripts to our package.json :

{
"scripts": {
"build:css": "node build-css.js",
"watch:css": "node build-css.js --watch"
},
// ...
}

For those unfamiliar with scripts inside a package.json : We can run these with npm run <command_name> . In this case build:css builds all files and watch:css spawns processes that check if the file has changed, and if so, compiles them again. You have to run watch:css manually, if you want to see the changes to CSS/HTML instantly. Otherwise, you have to restart the application.

We have to modify our main .csproj file as well to include the build step before the C# Code gets compiled. Add the following lines to your .csproj :

<Project>
<!-- ... -->
<!-- This command assumes that the tailwindcss folder is in the same folder as the .csproj. If it isn't, modify the prefix to point to the relevant folder. -->
<Target Name="StylesCompile" BeforeTargets="BeforeBuild" Condition="true">
<Exec ConsoleToMSBuild="true" Command="npm run --prefix tailwindcss build:css" />
</Target>
</Project>

Condition="true" forces the SCSS-build each time we start the app as by default dotnet doesn’t check for changes in our SCSS files.

And that’s it! Now we can use SCSS and TailwindCSS in our Blazor app with scoped CSS. During development start the application and run npm run watch:css inside your tailwindcss folder, so you can use hot reload as well.

Resources:

--

--