Typed CSS with Tailwind

The Tailwind CSS framework is really nice to use. I recommend checking out some of their instructional videos to get a sense of what it’s like to work with.

If you’re like me, just writing strings for css styling also fills you with anxiety. So let’s use types for our css! A bonus feature is that we now get autocomplete:

(hmm, it seems like embedded webm in discourse broke. Link below)

Here’s a template (tailwind branch) where you can try this for yourself:

git clone --branch tailwind https://github.com/milesfrain/halogen.git myApp
cd myApp
npm install -g purescript spago parcel tailwindcss
# or if you prefer local installation:
# npm install
npm run gen-css
npm run build
npm run serve

How it works

  1. gen-css calls tailwindcss to generate the enormous tailwind.css file. (There are additional customization options at this phase not covered in this guide).
  2. It also calls a css/css2purs.py script which generates a src/Tailwind.purs file containing wrapped tailwind class strings. This is a pretty huge file (50k lines). It uses halogen-compatible ClassName wrappers, but you can tweak it as you wish. Symbols are stripped out of the class strings and some other transformations are applied to turn them into valid PS function names. For example:
hoverNegTranslateX1d2 = ClassName "hover:-translate-x-1/2"
  1. Now autocomplete works, and you’re protected against string typos with your classes. One downside is that rebuilds are more sluggish due to the massive Tailwind.purs file. This is about 4 seconds on my system (versus 1 second without tailwind), which is pretty noticeable with automatic rebuild and page refresh.
    Edit: A compiler fix for this slowness issue has been merged, but if you’d like this faster behavior today (while waiting for 0.14.0), you can make a custom build from this branch, which applies the fix to 0.13.8. Reach out if you’d like more detailed instructions. The following section shows a workaround for vanilla 0.13.8.

Faster rebuilds

If you’re not planning on tweaking CSS while working on other parts of your project, and would like faster rebuilds, you can shrink the size of src/Tailwind.purs to only contain the CSS classes that are actually being used in your project by running:

npm run lock-css

Remember to also “Restart/Reconnect purs IDE server” so it picks-up this smaller file.

Note that now autocomplete will no-longer be aware of all possible CSS classes, so re-run gen-css when you’re ready again for the full CSS pallette.


The initial tailwind.css file is 2MB, but we can throw away all the unused classes. Simply run:

npm run build-prod

It runs PurgeCSS to look at what classes are actually used in the bundle produced by purs (with dead-code-elimination applied), and removes everything it doesn’t see from prod/tailwind.css. This is the same approach used by lock-css, except the purged css file is then fed back into css2purs.py to generate the input Tailwind.purs file.

In this template example the resulting CSS is shrunk down to 12KB (4KB minified, 1.5KB compressed).


Tailwind is pretty, but how about tables and everything else, there is no predefined components as I see

I plan to port rmwc for example

Btw you can generate purescript modules with https://github.com/purescript-codegen/purescript-ps-cst


You are missing the point of tailwind, here is tables -> https://tailwindcss.com/docs/table-layout/#app

and if you want to use extensible components have a look at https://tailwindui.com/

Tailwind is about extensibility, and be able to tweak. It is like abstraction in correct level, you can abstract table+padding+color into .custome-table and use that everywhere. It takes small time to get going this way, but you will thank to yourself that you did it, that way. No more !important everywhere where you need to fight with framework.


I’ve been working on a CLI inspired by this your tool:

It’s not very well documented now but it does the generation of the src/Tailwind.purs with the option to only generate the used classes (looks for T.className) and also to generate the optimized tailwind.css file only with the used styles.

1 Like

Nice work! A standalone app is definitely more convenient to use.

Hosting on NPM would make this even more accessible than installation via stack. That might require a port from .hs to .purs though. Here are some existing npm-hosted purs cli apps for reference:


Thanks! Yeah, I wanted to extend it more so standalone app made sense.

I added Nix file (default.nix) to make it a bit easier to consume (first time using Nix so no idea what I’m doing really :sweat_smile:).

But you are right npm makes things much easier for PS users. Maybe possible to distribute the binaries built with Haskell through npm (PS compiler does that, right?).

Yes, and so does Purty, but those are more complex to distribute, since you need a binary for every machine architecture you want to support. If written in PS, we’d get cross-platform support for free.

1 Like