Tailwind CSS

Introduction

Yes, while bootstrap is the default CSS framework in IHP, you can use IHP together with TailwindCSS (TW in short). This guide will help you set up the latest Tailwind version in your IHP project. We will be leveraging the TW’s JIT mode.

We will also have PostCSS added as part of the installation. PostCSS is used for nesting CSS, minifying, striping out comments, etc.

Installing

NodeJS

First, we need to add NodeJS to our project. You should also follow this step if you have NodeJS already installed on your system. Installing the NodeJS version via nix allows all people working on your project to get the same NodeJS version as you’re using.

For that open your projects default.nix and add nodejs to otherDeps:

otherDeps = p: with p; [
    # Native dependencies, e.g. imagemagick
    nodejs
];

Now you need to rebuild your local development environment:

nix-shell --run 'make -B .envrc'

After that, you have node and npm available in your project.

Installing Tailwind

Install Tailwind along with PostCSS and some handy libraries via NPM:

npm init
npm add tailwindcss@latest postcss@latest autoprefixer@latest @tailwindcss/forms

This will create package.json and package-lock.json. Make sure to commit both files to your git repository.

Configuring Tailwind

Create a new directory tailwind. We’re going to place all the CSS files and the tailwind configuration in that directory:

mkdir tailwind

Create the tailwind configuration file at tailwind/tailwind.config.js with the following content:

const plugin = require('tailwindcss/plugin');

module.exports = {
    mode: 'jit',
    theme: {
        extend: {
        },
    },
    content: [
        "Web/View/**/*.hs",
    ],
    safelist: [
        // Add custom class names.
        // https://tailwindcss.com/docs/content-configuration#safelisting-classes
    ],
    plugins: [
        require('@tailwindcss/forms'),
    ],
};

We also need a CSS entry point for Tailwind. Create a new file at tailwind/app.css.

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
  .btn {
    @apply px-4 py-2 bg-blue-600 text-white rounded;
  }
}

Adding the build step

We need to add a new build command for starting a tailwind build process to our Makefile. For that append this to the Makefile in your project:

tailwind-dev:
	node_modules/.bin/tailwind -c tailwind/tailwind.config.js -i ./tailwind/app.css -o static/app.css --watch

Make requires tab characters instead of 4 spaces in the second line. Make sure you’re using a tab character when pasting this into the file

This defines a new command make tailwind-dev that runs npx tailwindcss build whenever a CSS file inside the tailwind/ directory changes. The CSS output will be placed at static/app.css (the standard main CSS file of IHP apps). It will use the tailwind configuration at tailwind/tailwind.config.js.

For production builds, we also need a new make target:

node_modules:
    NODE_ENV=production npm ci

static/app.css:
	NODE_ENV=production node_modules/.bin/tailwind -c tailwind/tailwind.config.js -i ./tailwind/app.css -o static/app.css --minify

Make requires tab characters instead of 4 spaces in the second line. Make sure you’re using a tab character when pasting this into the file

You can now execute it with make -B static/app.css

Updating the .gitignore

As the static/app.css is now generated code, it’s best to put the static/app.css into our .gitignore file.

git rm -f static/app.css # Remove the existing app.css
printf '\nstatic/app.css' >> .gitignore
git add .gitignore

Removing bootstrap

Open Web/View/Layout.hs and remove the following <link> and <script> elements that load bootstrap:

<link rel="stylesheet" href="/vendor/bootstrap.min.css" />
<script src="/vendor/popper.min.js"></script>
<script src="/vendor/bootstrap.min.js"></script>

We don’t need to make any additions for Tailwind here. Just get rid of bootstrap.

Bootstrap is also part of the production CSS build, we need to remove that as well. Open Makefile and remove these lines:

CSS_FILES += ${IHP}/static/vendor/bootstrap.min.css
JS_FILES += ${IHP}/static/vendor/popper.min.js
JS_FILES += ${IHP}/static/vendor/bootstrap.min.js

Switching IHP Styling

Right now, your IHP app will still be rendered with some bootstrap CSS class names. We can switch this to use tailwind classes. Since our configuration uses the JIT mode, it means we would need to copy the tailwind CSSFramework from IHP core files, into our custom theme. Otherwise, any css defined by IHP itself will not be caught by Tailwind before purging and keeping only the used CSS classes. This might seem weird initially, having to copy/paste; however, we think it is the best compromise since it’s very likely you would like to change the default classes and get your site a unique view.

Create a file at Web/View/CustomCSSFramework.hs and copy the imports and the contents of the tailwind function from IHP/View/CSSFramework.hs:

module Web.View.CustomCSSFramework (customTailwind) where

import IHP.View.CSSFramework -- This is the only import not copied from IHP/View/CSSFramework.hs
import IHP.Prelude
import IHP.FlashMessages.Types
import qualified Text.Blaze.Html5 as Blaze
import Text.Blaze.Html.Renderer.Text (renderHtml)
import IHP.HSX.QQ (hsx)
import IHP.HSX.ToHtml ()
import IHP.View.Types
import IHP.View.Classes

import qualified Text.Blaze.Html5 as H
import Text.Blaze.Html5 ((!), (!?))
import qualified Text.Blaze.Html5.Attributes as A
import IHP.ModelSupport
import IHP.Breadcrumb.Types
import IHP.Pagination.Helpers
import IHP.Pagination.Types
import IHP.View.Types (PaginationView(linkPrevious, pagination))


-- Copying the contents of 'tailwind' function
customTailwind :: CSSFramework
customTailwind = def
    { styledFlashMessage
    , styledSubmitButtonClass
    , styledFormGroupClass
    , styledFormFieldHelp
-- ... Keep copying the rest of the function

Now JIT will recognize those classes and not purge them.

Open Config/Config.hs and make these changes:

module Config where

import IHP.Prelude
import IHP.Environment
import IHP.FrameworkConfig
import Web.View.CustomCSSFramework -- ADD THIS IMPORT

config :: ConfigBuilder
config = do
    option Development
    option (AppHostname "localhost")
    option customTailwind -- ADD THIS OPTION

Developing with Tailwind

Once everything is installed, you can start your tailwind build by calling:

make tailwind-dev

Whenever you change any CSS file in tailwind/ it will automatically rebuild your styles and write it to static/app.css.

Building for production

Because we defined the static/app.css make task above, the standard process of building IHP CSS applies here as usual:

make static/prod.css

Make will automatically detect that static/app.css is missing and will run make static/app.css to produce that missing file. This will then trigger the tailwind production build.

This means you don’t need to make any changes to your existing deployment process or your IHP Cloud settings.