codegino
← all posts

Styling Remix using Tailwind and PostCSS

How to style a Remix app using Tailwind and PostCSS

4 min read#react#tech#remix

wind turbines
wind turbines

TL;DR: Source and Demo

Here's a live demo

Link to the source code

Link to step by step commits


Introduction

In my last blog post, I discussed how to style a Remix app using Vanilla CSS. This blog will show how to integrate Tailwind and PostCSS into our Remix app.


Dependencies

Installation

sh
npm install -D autoprefixer postcss postcss-cli postcss-import tailwindcss cssnano

OR if you prefer yarn

sh
yarn add -D autoprefixer postcss postcss-cli postcss-import tailwindcss cssnano

Add scripts to package.json


Add Script for CSS generation

package.json
1"scripts": {2  // ...3  "css:watch": "npm run css:build -- --watch",4  "css:build": "postcss styles/**/*.css --dir app/styles",5  "css:prod": "npm run css:build -- --env production",6  // ...7},

Replace npm run with yarn if you prefer to use yarn

I don't want to commit those generated CSS files to the repo, so I'll be adding them to .gitignore

.gitignore
app/styles/*.css

Add Script for cleaning up build files

package.json
1"scripts": {2  // ...3  "build": "npm run css:prod && remix build",4  "prebuild": "rimraf ./public/build \"./app/styles/**/*.css\""5  // ...6},

Running the scripts

  • Development

Run npm run css:watch in one terminal and remix dev in another

sh
1npm run css:watch
sh
1npm run dev

DISCLAIMER: Don't expect it will work immediately. We still need to configure a few things with Tailwind and PostCSS.

OPTIONAL: Run multiple scripts in a single command

  • Production
sh
1npm run build

If you are not a fan of multiple terminals, use concurrently to run css:watch and remix dev in parallel

package.json
1"scripts": {2  // ...3  "dev": "concurrently npm run css:watch && remix dev",4  // ...5}

Tailwind and App styles presets


Tailwind styles

We need to explicitly declare the features we want to use in our CSS. Here's a reference of what you can use.

styles/tailwind.css
1@tailwind base;2@tailwind components;3@tailwind utilities;4@tailwind screens;

App CSS presets

Some CSS defaults I prefer

styles/app.css
1:root {2  --color-primary-light: hsl(210, 100%, 98%);3  --color-primary-100: hsl(210, 100%, 95%);4  --color-primary-200: hsl(210, 100%, 85%);5  --color-primary-300: hsl(210, 100%, 80%);6  --color-primary-400: hsl(210, 100%, 75%);7  --color-primary-500: hsl(210, 100%, 60%);8  --color-primary-600: hsl(210, 100%, 50%);9  --color-primary-700: hsl(210, 100%, 40%);10  --color-primary-800: hsl(210, 100%, 30%);11  --color-primary-900: hsl(210, 100%, 20%);12  --color-primary-dark: hsl(210, 100%, 2%);13}14
15input,16select,17textarea {18  @apply text-black;19}20
21@media (prefers-color-scheme: dark) {22  html {23    @apply bg-black text-white;24  }25}

PostCSS and Tailwind configuration


PostCSS Config File

postcss.config.js
1module.exports = {2  plugins: [3    require("tailwindcss"),4    require("autoprefixer"),5    require("postcss-import"),6    process.env.NODE_ENV === "production" &&7      require("cssnano")({8        preset: "default",9      }),10  ],11};

Tailwind Config File

tailwind.config.js
1module.exports = {2  mode: process.env.NODE_ENV ? "jit" : undefined,3  // To purge CSS in .ts .tsx files4  purge: ["./app/**/*.{ts,tsx}"], 5  darkMode: "media", // Use media queries for dark mode6  theme: {7    extend: {8      colors: {9        // color scheme is defined in /app.css10        // To enable text-primary-xxx, bg-primary-xxx, or border-primary-xxx11        primary: {12          light: "var(--color-primary-light)",13          100: "var(--color-primary-100)",14          200: "var(--color-primary-200)",15          300: "var(--color-primary-300)",16          400: "var(--color-primary-400)",17          500: "var(--color-primary-500)",18          600: "var(--color-primary-600)",19          700: "var(--color-primary-700)",20          800: "var(--color-primary-800)",21          900: "var(--color-primary-900)",22          dark: "var(--color-primary-dark)",23        },24      },25    },26  },27  variants: {}, // activate any variant you want here28  plugins: [], // add any plugin you need here29};

Integrating styles in Remix Code

Add a reference of the generated CSS files using links in app/root.tsx

app/root.tsx
1// ...2import type { LinksFunction } from "remix";3import tailwindStyles from "~/styles/tailwind.css";4import appStyles from "~/styles/app.css";5
6export let links: LinksFunction = () => {7  return [8    { rel: "stylesheet", href: tailwindStyles },9    {10      rel: "stylesheet",11      href: appStyles,12    },13  ];14};15// ...

Styling a component

Use Tailwind, as usual; add Tailwind's class names added inside the className prop.

app/components/word-form/index.tsx
1import { Form, useTransition } from "remix";2import { Word, WordType } from "~/models/word";3import { Button } from "../basic/button";4import { Input } from "../basic/input";5import { Select } from "../basic/select";6import { TextArea } from "../basic/textarea";7
8export function WordForm({ word }: { word?: Word }) {9  let transition = useTransition();10
11  return (12    <Form13      method="post"14      className={`15        px-3 py-4 rounded flex flex-col gap-2 border-216      `}17    >18      <div>Form State: {transition.state}</div>19      <div>20        <label className="block text-xs" htmlFor="name">21          Word22        </label>23        <Input24          id="name"25          name="name"26          type="text"27          placeholder="Word"28          required29          defaultValue={word?.name ?? ""}30          disabled={Boolean(word?.name)}31        />32      </div>33      <div>34        <label className="block text-xs" htmlFor="type">35          Type36        </label>37        <Select38          id="type"39          name="type"40          defaultValue={word?.type ?? WordType.NOUN}41        >42          <option value={WordType.NOUN}>Noun</option>43          <option value={WordType.VERB}>Verb</option>44          <option value={WordType.ADJECTIVE}>Adjective</option>45        </Select>46      </div>47      {/*TextAreas*/}48      <Button type="submit" color="primary">49        Submit50      </Button>51    </Form>52  );53}54// ...

If you're wondering where the above file came from, that is from my last blog post.


VSCode Plugins

Here are some plugins that you can use to get a better experience using Tailwind and PostCSS in VSCode.


Conclusion

Integrating Tailwind and PostCSS in Remix is straightforward as we don't need to hack into the framework to make them work. We quickly achieved an extendable and customizable CSS generation boilerplate by adding a few configurations.