DEV Community

Cover image for Kentico Xperience Xplorations: Using Tailwind CSS with Kentico Xperience
Sean G. Wright
Sean G. Wright

Posted on

Kentico Xperience Xplorations: Using Tailwind CSS with Kentico Xperience

In this post we'll be exploring what it looks like to use Tailwind CSS in our Kentico Xperience 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, and declarative programming provides a powerful API that hides away the brain 🧠 melting 😱 complexity 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 post 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 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.

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, have Node.js 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:

> cd .\Sandbox.Web\
> Sandbox.Web
>
Enter fullscreen mode Exit fullscreen mode

Then we install our Node.js Tailwind CSS dependency with npm:

> npm install -D tailwindcss
Enter fullscreen mode Exit fullscreen mode

Once this installation completes we can initialize our Tailwind configuration:

> npx tailwindcss init
Enter fullscreen mode Exit fullscreen mode

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:

module.exports = {  
  content: ["./**/*.{cshtml}"],  
  theme: {
    extend: {},  
  },
  plugins: [],
};
Enter fullscreen mode Exit fullscreen mode

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:

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

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 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:

{
  /* ... */

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

  /* ... */
}
Enter fullscreen mode Exit fullscreen mode

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 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:

<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>
Enter fullscreen mode Exit fullscreen mode

Whenever a Release build of our application is initiated, first 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 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 (or 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, 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 😔:

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

<div class="@(classColor + "-500")">
  <!-- ... -->
</div>
Enter fullscreen mode Exit fullscreen mode

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

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

<div class="@classColor">
  <!-- ... -->
</div>
Enter fullscreen mode Exit fullscreen mode

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 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 C

Kentico Xperience's Page Builder components (Widgets, Sections, Page Template 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:

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; } = "";
}
Enter fullscreen mode Exit fullscreen mode

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 😊:

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

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

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

<div class="@backgroundColor">
  <!-- ... -->
</div>
Enter fullscreen mode Exit fullscreen mode

💻 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, 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 to compose Tailwind's utility classes into custom CSS rules
  • Safelist 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 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 🙏!


Photo by Jordan Madrid on Unsplash

References


We've put together a list over on Kentico's GitHub account of developer resources. Go check it out!

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

#kentico

#xperience

Or my Kentico Xperience blog series, like:

Discussion (0)