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
androtate-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
>
Then we install our Node.js Tailwind CSS dependency with npm:
> npm install -D tailwindcss
Once this installation completes we can initialize our Tailwind configuration:
> 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:
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:
@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 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"
}
/* ... */
}
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>
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>
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>
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 CSharp
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; } = "";
}
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; } = "";
}
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>
💻 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 🙏!
References
- Tailwind CSS
- CSS Cascade on MDN
- Inside a super fast CSS engine: Quantum CSS
- Adam Wathan on Twitter
- CSS Utility Classes and "Separation of Concerns" 🌟
- Using Tailwind via CDN on Tailwind Docs
- Using Tailwind via CLI on Tailwind Docs
- Windows Terminal on Microsoft Docs
- Download Node.js
- Reusing Styles with Tailwind CSS on Tailwind Docs
- ASP.NET Core Fundamentals: Webroot on Microsoft Docs
- Npm ci on npm Docs
- Editor Setup on Tailwind Docs
- VS Code Download
- JetBrains Riders
- Dynamic Class Names on Tailwind Docs
- Safelisting Classes on Tailwind Docs
- Assigning Editing Components to Properties on Kentico Xperience Docs
- Content Driven Development: Or, Why the WYSIWYG Can Be a Trap (Video) from Kentico Xperience Connections 2020
- Configuring the Rich Text Editor on Kentico Xperience Docs
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.
Or my Kentico Xperience blog series, like:
Top comments (0)