Kentico Xperience Xplorations: Using Tailwind CSS with Kentico Xperience
Tags: xperience, css, aspnetcore, tailwindcss
Published: 2022-01-24 15:15:11

---
title: Kentico Xperience Xplorations: Using Tailwind CSS with Kentico Xperience
published: true
description: Why might we want to use Tailwind CSS with our Kentico Xperience sites? How do we integrate it? And, what pitfalls should we look out for?
tags: xperience, css, aspnetcore, tailwindcss
series: Kentico Xperience Xplorations
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/algcgsbuf2z4hv65yrw7.jpg
---

> In this post we'll be exploring what it looks like to use [Tailwind CSS](https://tailwindcss.com/) in our [Kentico Xperience](https://docs.xperience.io/developing-websites/developing-xperience-applications-using-asp-net-core) applications.

## 🧭 Starting Our Journey: Strategies for CSS

From Tailwind CSS's home page, it defines itself as:

> A utility-first CSS framework packed with classes like `flex`, `pt-4`, `text-center` and `rotate-90` that can be composed to build any design, directly in your markup.

But, before we dive into setting up and using Tailwind CSS with Kentico Xperience, let's explore _why_ 🤔 we would use it instead of other approaches to CSS development.

### 🔬 The True Nature of CSS

CSS, as a language, is declarative and global. Developers that are new to CSS or more comfortable in JavaScript often dislike CSS' declarative and global nature.

However, if we look at things holistically, we can see that being global is what enables the power of [the cascade](https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade), and declarative programming provides a powerful API that hides away the [brain 🧠 melting 😱 complexity](https://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo/) of browser rendering engines.

That said, CSS does need to be managed, especially in larger projects that are expected to evolve (and be maintainable) over time.

## 🔩 CSS Utility Classes

One of my favorite blog posts ever about CSS and HTML is [Adam Wathan's](https://twitter.com/adamwathan) post [CSS Utility Classes and "Separation of Concerns"](https://adamwathan.me/css-utility-classes-and-separation-of-concerns/) 🤩.

Adam Wathan is the creator of Tailwind CSS, and an experienced web developer, so it's probably worth checking out his perspective on CSS if only to broaden our own... 🧐

> I recommend following the link above and reading the post **in full**. Using Tailwind CSS has, unfortunately, become a very polarizing topic, I think primarily because developers often express their opinions without expressing the context that leads to those opinions. Do yourself a favor 👍🏽 and gain a little context!

---

Adam reflects on the CSS/HTML "separation of concerns" in his post:

> When you think about the relationship between HTML and CSS in terms of "separation of concerns", it's very black and white.

> You either have separation of concerns (good!), or you don't (bad!).

> This is not the right way to think about HTML and CSS.

> Instead, think about dependency direction.

> **CSS that depends on HTML**.
> Naming your classes based on your content (like .author-bio) treats your HTML as a dependency of your CSS.

> **HTML that depends on CSS**.
> Naming your classes in a content-agnostic way after the repeating patterns in your UI (like .media-card) treats your CSS as a dependency of your HTML.

He then poses this comparison:

> CSS Zen Garden takes the first approach, while UI frameworks like Bootstrap or Bulma take the second approach.

> Neither is inherently "wrong"; it's just a decision made based on what's more important to you in a specific context.

And, asks a simple question ❔:

> For the project you're working on, what would be more valuable: restyleable HTML, or reusable CSS?

The goal of having more reusable CSS at the cost of re-stylable HTML is where Tailwind CSS as a framework comes from.

If this aligns with the goals of our project, then Tailwind might be a good fit 😃, and we can continue reading to learn how to setup Tailwind CSS with a Kentico Xperience ASP.NET Core project...

## 🗺 Kentico Xperience + Tailwind CSS

Tailwind CSS can be used by loading its CSS [from a CDN](https://tailwindcss.com/docs/installation/play-cdn) but as noted in the documentation:

> The Play CDN is designed for development purposes only, and is not the best choice for production.

So, instead, we're going to focus on using [Tailwind as a Node.js CLI tool](https://tailwindcss.com/docs/installation).

We can follow the documentation in the previous link with a few minor annotations and additions.

> Note: These steps assume you are comfortable using the [command line](https://docs.microsoft.com/en-us/windows/terminal/), have [Node.js](https://nodejs.org/en/) installed, and have some familiarity with client-side tools and development.

First, we'll want to make sure we've changed directory to our Kentico Xperience ASP.NET Core projects at the command line because our Tailwind integration is only for the content presentation (ASP.NET Core) application:

```bash
> cd .\Sandbox.Web\
> Sandbox.Web
>
```
Then we install our Node.js Tailwind CSS dependency with npm:

```bash
> npm install -D tailwindcss
```

Once this installation completes we can initialize our Tailwind configuration:

```bash
> npx tailwindcss init
```

Tailwind's build process generates a CSS file with the minimal number of CSS classes and style rules possible by scanning the HTML templates of an application and only adding the utility classes used in those templates 🤓.

In our `tailwind.config.js`, we customize it for our ASP.NET Core specific use-case by telling Tailwind it should look for its CSS utility classes in all of our Razor files:

```js
module.exports = {  
  content: ["./**/*.{cshtml}"],  
  theme: {
    extend: {},  
  },
  plugins: [],
};
```

We then create an entry-point `site.css` file at the root of our ASP.NET Core project where we specify the parts of Tailwind CSS we'd like to include in our project:

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

> Note: You can also use this entry-point file to define custom CSS classes. However, I'd encourage you to attempt to read [Tailwind's documentation on CSS reusability](https://tailwindcss.com/docs/reusing-styles) and try to meet your CSS requirements with the library's utility classes first.

Now, we update our `package.json` with some scripts to run the Tailwind command on our project:

```json
{
  /* ... */

  "scripts": {
    "start": "tailwindcss -i ./site.css -o ./wwwroot/site.css --watch",
    "build": "tailwindcss -i ./site.css -o ./wwwroot/site.css --minify"
  }

  /* ... */
}
```

We tell Tailwind to output the generated CSS file into the `./wwwroot` folder because [it is the only directory that ASP.NET Core serves static files](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/?view=aspnetcore-6.0&tabs=windows#web-root) from by default.

It's also convenient that all files in `./wwwroot` will automatically be published with the application without any additional build configuration.

There are 2 "scripts", `start` which we can run with `npm start`, at the command line, for local development, and `build` which we run with `npm run build` for production.

Last, we'll want to integrate this npm tooling into MSBuild so that when our ASP.NET Core application is built in `Release` mode, the production-ready CSS is also generated.

> This can be very helpful if we are using CI/CD for build and deployment of our Kentico Xperience application.

We'll make the following addition to the bottom of our ASP.NET Core's `.csproj` file:

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

  <!-- other settings -->

  <Target Name="NpmInstall" BeforeTargets="BuildCSS" Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <Exec Command="npm ci" />
  </Target>

  <Target Name="BuildCSS" BeforeTargets="BeforeBuild" Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <Exec Command="npm run build" />
  </Target>

</Project>
```

Whenever a `Release` build of our application is initiated, first [npm ci](https://docs.npmjs.com/cli/v8/commands/npm-ci) will be run first, followed by `npm run build` to kick off our CSS production build command 👏🏾.

## 🌵 Ouch: Kentico Xperience + Tailwind CSS Pitfalls

### 🥊 Visual Studio vs VS Code

Tailwind CSS includes thousands of permutations of utility CSS classes, which creates a CSS API of sorts that developers have to learn to be effective with the library.

It's [recommended](https://tailwindcss.com/docs/editor-setup#intelli-sense-for-vs-code) to use an editor when authoring HTML that supports intellisense for Tailwind CSS class names in our HTML and PostCSS's special syntax when writing custom CSS rules.

Currently, [VS Code](https://code.visualstudio.com/) (or [Rider](https://www.jetbrains.com/rider/)) is the best option 😎. Visual Studio has no extension support 😑.

However, Visual Studio is a best-in-class IDE for editing C# and Razor, so developers will have to choose whether they want to prioritize the authoring experience for back-end C# or their front-end Tailwind CSS 🤷‍♂️.

> I think front-end tooling is an area where Visual Studio is currently very weak compared to other products, but if your projects don't have much in the way of front-end code and dependencies, this might not matter to you.

> My personal preference is to use VS Code as much as I can for any front-end development. I write a lot of TypeScript, JavaScript, SCSS, and use lots of npm scripts and VS Code extensions. I even _exclude_ front-end files from the .NET project so that I can't see them in Visual Studio 😉. This forces me to use VS Code (and all of its wonderful extensions and tooling) for this part of a project.

### 🛠 Dynamic CSS Class Generation

As mentioned earlier, Tailwind CSS scans our project HTML template source files for recognizable CSS classes and then includes those in the generated output CSS file.

However, Tailwind [cannot recognize dynamically composed CSS classes](https://tailwindcss.com/docs/content-configuration#dynamic-class-names), which means we need to consider how creative we get in our Razor files when building our HTML templates.

For example, the following won't work because Tailwind needs to statically recognize at development time what classes we are using - it can't determine what will be used at runtime 😔:

```html
@{
   string classColor = string.Equals(color, "red")
      ? "red"
      : "blue";
}

<div class="@(classColor + "-500")">
  <!-- ... -->
</div>
```

We'd need to use the following to get the desired result:

```html
@{
   string classColor = string.Equals(color, "red")
      ? "red-500"
      : "blue-500";
}

<div class="@classColor">
  <!-- ... -->
</div>
```

This can feel like a hoop we have to jump through to get Tailwind to work correctly, and in a way, it is. But this extra work means the final production-ready CSS file is extremely small and optimized 💪🏾.

As a final workaround, we can [safelist](https://tailwindcss.com/docs/content-configuration#safelisting-classes) specific classes we know are being used but Tailwind can't find when it scans our templates. This should be avoided if possible because it means we could be shipping unused CSS if we forget to keep the safelist up to date 😣.

### 👩🏽‍💻 Referencing CSS Classes in CSharp

Kentico Xperience's Page Builder components ([Widgets, Sections, Page Template Properties](https://docs.xperience.io/developing-websites/form-builder-development/assigning-editing-components-to-properties)) let us provide Content Managers with many customization points to build Pages that are flexible in both content and design 😎.

Unfortunately, if we aren't careful we could end up referencing Tailwind classes in places that aren't visible to Tailwind's tooling.

If we had some Page Template Properties that let a Content Manager customize the design of a page, we could back ourselves into a corner:

```csharp
public class HomePageTemplateProperties : IPageTemplateProperties
{
    [EditingComponent(DropDownComponent.IDENTIFIER)]
    [EditingComponentProperty(
        nameof(DropDownProperties.DataSource), 
        "bg-green-300;Green\r\nbg-red-500;Red")]
    public string BackgroundColor { get; set; } = "";
}
```

These properties reference the Tailwind classes, but since this code is in a C# file and Tailwind is configured to scan `.cshtml` files, it won't ever detect that our site is using them and they won't be included in the generated CSS 😕.

However, even if we weren't using Tailwind CSS storing CSS classes in component Properties **is a bad idea**. The approach above ties your component to a very specific design, makes this component much less reusable, and makes future design updates more difficult.

Instead, we should store values that represent our design choices and leave the selection of the actual CSS class up to the Razor View. Even better, avoid the specific color values and use more semantic terms 😊:

```csharp
public class HomePageTemplateProperties : IPageTemplateProperties
{
    [EditingComponent(DropDownComponent.IDENTIFIER)]
    [EditingComponentProperty(
        nameof(DropDownProperties.DataSource), 
        "primary;Primary\r\nsecondary;Secondary")]
    public string BackgroundColor { get; set; } = "";
}
```

Then, in our Razor View we can render the CSS class that matches the chosen option:

```html
@{
    string backgroundColor = Model.BackgroundColor switch
    {
        "primary" => "bg-green-300",
        "secondary" => "bg-red-500",
        _ => ""
    };
}

<div class="@backgroundColor">
  <!-- ... -->
</div>
```

### 💻 CSS Classes in the Database

I imagine a lot of you have already been thinking "How do we handle CSS classes that are stored in the database?" It's easy enough to take the semantic approach we just looked at and use it for Page Type fields and CMS Settings...

But, what about Rich Text?

Yeah, [Rich Text is problematic for many reasons](https://youtu.be/b7cYeIL1ZcU), but it's a requirement that most sites can't avoid.

Even if we were not using a CSS framework like Tailwind, we'd run into issues maintaining our custom CSS while having to support Rich Text 😮.

It's a lot easier to do a search in an IDE for a character sequence to see if a CSS class is still being used (and how it's being used) than querying dozens of database tables and columns looking for the same information in Rich Text content.

This is probably one situation where using Tailwind's escape hatches is the best option and we can follow these tips to avoid any real headaches:

- [Use @apply](https://tailwindcss.com/docs/reusing-styles#extracting-classes-with-apply) to compose Tailwind's utility classes into custom CSS rules
- [Safelist](https://tailwindcss.com/docs/content-configuration#safelisting-classes) and CSS classes we know will show up in Rich Text (even if they appear in our HTML templates).
- Encourage Content Managers to use Rich Text to structure free-form content and only apply styling minimally (this should always be encouraged)
- [Customize the Rich Text editor](https://docs.xperience.io/configuring-xperience/configuring-the-environment-for-content-editors/configuring-the-rich-text-editor-for-page-builder/customizing-the-rich-text-editor-toolbar) in Xperience to create UI elements that help Content Managers use the correct CSS classes on their Rich Text if they are required

## ✈ Heading Home: Is Xperience + Tailwind CSS a Good Idea?

As we've seen, setting up Tailwind CSS in a Kentico Xperience ASP.NET Core project is fairly simple, so when considering whether or not we should use Tailwind for our project, it's more a question of maintenance and growth than getting started.

Tailwind CSS is a powerful tool designed for a specific scenario - web applications where reusable CSS is more important than re-styleable HTML 🧐.

If this doesn't match our project, then Tailwind might not be the best tool for helping us author and maintain CSS. If it is, then there are a lot of benefits it can bring to our development experience 🤠.

However, there are also quite a few pitfalls we saw when exploring how we might use Tailwind with Kentico Xperience's features 😬.

We saw there are solutions to these potential problems, and that these solutions aren't only useful with Tailwind CSS - they are good practices for managing design and content in any Kentico Xperience site. It just so happens that with Tailwind they become more apparent.

As always, thanks for reading 🙏!

---

<figcaption>Photo by <a href="https://unsplash.com/@jordanmadrid">Jordan Madrid
</a> on <a href="https://unsplash.com">Unsplash</a></figcaption>

---

## References

- [Tailwind CSS](https://tailwindcss.com/)
- [CSS Cascade](https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade) on MDN
- [Inside a super fast CSS engine: Quantum CSS](https://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo/)
- [Adam Wathan](https://twitter.com/adamwathan) on Twitter
- [CSS Utility Classes and "Separation of Concerns"](https://adamwathan.me/css-utility-classes-and-separation-of-concerns/) 🌟
- [Using Tailwind via CDN](https://tailwindcss.com/docs/installation/play-cdn) on Tailwind Docs
- [Using Tailwind via CLI](https://tailwindcss.com/docs/installation) on Tailwind Docs
- [Windows Terminal](https://docs.microsoft.com/en-us/windows/terminal/) on Microsoft Docs
- [Download Node.js](https://nodejs.org/en/)
- [Reusing Styles with Tailwind CSS](https://tailwindcss.com/docs/reusing-styles) on Tailwind Docs
- [ASP.NET Core Fundamentals: Webroot](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/?view=aspnetcore-6.0&tabs=windows#web-root) on Microsoft Docs
- [Npm ci](https://docs.npmjs.com/cli/v8/commands/npm-ci) on npm Docs
- [Editor Setup](https://tailwindcss.com/docs/editor-setup#intelli-sense-for-vs-code) on Tailwind Docs
- [VS Code Download](https://code.visualstudio.com/)
- [JetBrains Riders](https://www.jetbrains.com/rider/)
- [Dynamic Class Names](https://tailwindcss.com/docs/content-configuration#dynamic-class-names) on Tailwind Docs
- [Safelisting Classes](https://tailwindcss.com/docs/content-configuration#safelisting-classes) on Tailwind Docs
- [Assigning Editing Components to Properties](https://docs.xperience.io/developing-websites/form-builder-development/assigning-editing-components-to-properties) on Kentico Xperience Docs
- [Content Driven Development: Or, Why the WYSIWYG Can Be a Trap (Video)](https://youtu.be/b7cYeIL1ZcU) from Kentico Xperience Connections 2020
- [Configuring the Rich Text Editor](https://docs.xperience.io/configuring-xperience/configuring-the-environment-for-content-editors/configuring-the-rich-text-editor-for-page-builder/customizing-the-rich-text-editor-toolbar) on Kentico Xperience Docs

---

We've put together a list over on [Kentico's GitHub account](https://github.com/Kentico/Home/blob/master/RESOURCES.md) of developer resources. Go check it out!

If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.

{% tag kentico %}

{% tag xperience %}

Or my [Kentico Xperience blog series](https://dev.to/seangwright/series), like:

- [Kentico Xperience Design Patterns](https://dev.to/seangwright/series/10963)
- [Kentico Xperience MVC Widget Experiments](https://dev.to/seangwright/series/9483)
- [Bits of Xperience](https://dev.to/seangwright/series/8740)