Building Common Lisp web apps with Tailwind CSS

In this post, I am going to walk you through to setup Tailwind CSS for a Common Lisp web application using Caveman. If you want to know more about creating web applications using Common Lisp and Caveman, please check my previous posts on the topic.

Project scaffolding using Caveman

Once you have Caveman installed with Quicklisp, you can generate a new project like this.

(caveman2:make-project #P"~/quicklisp/localprojects/tailwind-demo" :author "Rajasegar")

Why Tailwind CSS?

Tailwind CSS is a utility-first CSS framework packed with a ton of utility classes that can be composed to build any design, directly in your markup. It allows you to rapidly build modern websites without ever leaving your HTML. It has got extensive support for responsive design, themes, dark-mode and much more required to craft modern user interfaces for the web.

And with using a framework like Caveman for Common lisp web development, which is utilizing the Djula Templating system built on top of HTML, Djula Templates with Tailwind CSS is the perfect combination to quickly prototype and build beautiful web sites without writing a single line of CSS.

And moreover, it comes with a very small footprint of generated CSS, since you purge all the unwanted styles from your application during the build process. You can get a ton of styles for our app with less than 10 kb of overall file size. Your mileage may vary if you use heavy theming and lots of colors for your web app, but the average size hovers around 10 kb I guess. This is much much less than other standard CSS framework footprint and helps to load your websites faster.

Pre-requisites

You need Node.js installed on your machine. Because Tailwind CSS requires Node to run the programs to compile your CSS. It is kind of common to install Node.js for all the Javascript and CSS tooling, programs like npm and npx require Node, so please ensure you have installed the latest Node version in your machine.

Generating Tailwind config

Once you have bootstrapped your project using Caveman, it's time to initialize the Tailwind config for your project. Run the following command from your project root folder in the terminal.

npx tailwindcss init

This will create a minimal tailwind.config.js file at the root of your project:

module.exports = {
  purge: [],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
}

Creating our project CSS file

Create a CSS file in your src folder naming tailwind.css, and use the @tailwind directive to inject Tailwind’s base, components, and utilities styles:

/* ./src/tailwind.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

Tailwind will swap these directives out at build-time with all of the styles it generates based on your configured design system. We will use this file to generate/compile the final CSS and put into the standard css file that comes with Caveman, static/css/main.css, so that it will be automatically picked by the default layout in Djula.

Generating the output CSS

Once we have initiated the Tailwind config file and created the project css file, now we can use the tailwindcss CLI tool to compile our final css. We need to provide the input and output files to the CLI to generate the CSS build.

npx tailwindcss -i ./src/tailwind.css -o ./static/css/main.css

Once you run the above command in the terminal, your final CSS build will be generated in the path specified in the output option. And now you can start using Tailwind classes in your Djula templates. Let's add some markup to your index.html template to check whether the styles are working or not.

{% extends "layouts/default.html" %}
{% block title %}Welcome to Caveman2{% endblock %}
{% block content %}
<div class="min-h-screen bg-gray-100 py-6 flex flex-col justify-center sm:py-12">
  <div class="relative py-3 sm:max-w-xl sm:mx-auto">
    <div class="absolute inset-0 bg-gradient-to-r from-blue-400 to-blue-500 shadow-lg transform -skew-y-6 sm:skew-y-0 sm:-rotate-6 sm:rounded-3xl"></div>
    <div class="relative px-4 py-10 bg-white shadow-lg sm:rounded-3xl sm:p-20">
      <div class="max-w-md mx-auto">
        <div>
          <img src="https://play.tailwindcss.com/img/logo.svg" class="h-7 sm:h-8" />
        </div>
        <div class="divide-y divide-gray-200">
          <div class="py-8 text-base leading-6 space-y-4 text-gray-700 sm:text-lg sm:leading-7">
            <p>An advanced online playground for Tailwind CSS, including support for things like:</p>
            <ul class="list-disc space-y-2">
              <li class="flex items-start">
                <span class="h-6 flex items-center sm:h-7">
                  <svg class="flex-shrink-0 h-5 w-5 text-blue-500" viewBox="0 0 20 20" fill="currentColor">
                    <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
                  </svg>
                </span>
                <p class="ml-2">
                  Customizing your
                  <code class="text-sm font-bold text-gray-900">tailwind.config.js</code> file
                </p>
              </li>
              <li class="flex items-start">
                <span class="h-6 flex items-center sm:h-7">
                  <svg class="flex-shrink-0 h-5 w-5 text-blue-500" viewBox="0 0 20 20" fill="currentColor">
                    <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
                  </svg>
                </span>
                <p class="ml-2">
                  Extracting classes with
                  <code class="text-sm font-bold text-gray-900">@apply</code>
                </p>
              </li>
              <li class="flex items-start">
                <span class="h-6 flex items-center sm:h-7">
                  <svg class="flex-shrink-0 h-5 w-5 text-blue-500" viewBox="0 0 20 20" fill="currentColor">
                    <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
                  </svg>
                </span>
                <p class="ml-2">Code completion with instant preview</p>
              </li>
            </ul>
            <p>Perfect for learning how the framework works, prototyping a new idea, or creating a demo to share online.</p>
          </div>
          <div class="pt-6 text-base leading-6 font-bold sm:text-lg sm:leading-7">
            <p>Want to dig deeper into Tailwind?</p>
            <p>
              <a href="https://tailwindcss.com/docs" class="text-blue-600 hover:text-cyan-700"> Read the docs &rarr; </a>
            </p>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
{% endblock %}

I have copied the template from the Tailwind CSS playground. Once you saved the changes to our index.html file, it's time to start our web server and see the page in action.

(tailwind-demo:start :port 3000)

You should get a page looking like this.
Alt Text

Generating Production CSS

Now you have played around with Tailwind CSS in your templates, and once your are ready to deploy your app, we should optimize our css builds by tree-shaking unused styles and minify our final CSS builds.

So, once again we are going to use the tailwindcss CLI for the same, but with a different set of options.

Before that we need to update our tailwind config to point our template files path to the purge option, so that the CLI can properly remove the unwanted styles from the final production build.

Our tailwind config will now look like this, with the purge option enabled. Actually we are pointing the path of our Djula templates with a wildcard expression for all HTML files under all sub-directories.

module.exports = {
  purge: ['./templates/**/*.html],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
}

Now you can run the following command to generate the production build for our CSS.

NODE_ENV=production npx tailwindcss -i ./src/tailwind.css -o ./static/css/main.css --minify

Note the NODE_ENV option with production as the value, without this option the tailwindcss CLI won't purge the unused styles. And for minifying the CSS we pass the --minify parameter.

You can also put these commands into a separate shell scripts and invoke them whenever you want.

build-prod-css.sh

#!/bin/sh
NODE_ENV=production npx tailwindcss -i ./src/tailwind.css -o ./static/css/main.css --minify

Give the script executable permissions so that you can run them later.

chmod +x ./build-prod-css.sh

And you can invoke the script like this:

./build-prod-css.sh

That's it, now you can commit the generated CSS file and deploy it to production environment.

The source code for this tutorial is hosted in Github.

22