DEV Community

Carlo Gino Catapang
Carlo Gino Catapang

Posted on • Originally published at codegino.com

Styling Remix using Tailwind and PostCSS

Table of Contents

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

npm install -D autoprefixer postcss postcss-cli postcss-import tailwindcss cssnano
Enter fullscreen mode Exit fullscreen mode

OR if you prefer yarn

yarn add -D autoprefixer postcss postcss-cli postcss-import tailwindcss cssnano
Enter fullscreen mode Exit fullscreen mode

Add scripts to package.json

Add Script for CSS generation

// package.json
"scripts": {
  // ...
  "css:watch": "npm run css:build -- --watch",
  "css:build": "postcss styles/**/*.css --dir app/styles",
  "css:prod": "npm run css:build -- --env production",
  // ...
},
Enter fullscreen mode Exit fullscreen mode

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

app/styles/*.css
Enter fullscreen mode Exit fullscreen mode

Add Script for cleaning up build files

// package.json
"scripts": {
  // ...
  "build": "npm run css:prod && remix build",
  "prebuild": "rimraf ./public/build \"./app/styles/**/*.css\""
  // ...
},
Enter fullscreen mode Exit fullscreen mode

Running the scripts

  • Development

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

npm run css:watch
Enter fullscreen mode Exit fullscreen mode
npm run dev
Enter fullscreen mode Exit fullscreen mode

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
npm run build
Enter fullscreen mode Exit fullscreen mode

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

// package.json
"scripts": {
  // ...
  "dev": "concurrently npm run css:watch && remix dev",
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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 */
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind screens;
Enter fullscreen mode Exit fullscreen mode

App CSS presets

Some CSS defaults I prefer

/* styles/app.css */
:root {
  --color-primary-light: hsl(210, 100%, 98%);
  --color-primary-100: hsl(210, 100%, 95%);
  --color-primary-200: hsl(210, 100%, 85%);
  --color-primary-300: hsl(210, 100%, 80%);
  --color-primary-400: hsl(210, 100%, 75%);
  --color-primary-500: hsl(210, 100%, 60%);
  --color-primary-600: hsl(210, 100%, 50%);
  --color-primary-700: hsl(210, 100%, 40%);
  --color-primary-800: hsl(210, 100%, 30%);
  --color-primary-900: hsl(210, 100%, 20%);
  --color-primary-dark: hsl(210, 100%, 2%);
}

input,
select,
textarea {
  @apply text-black;
}

@media (prefers-color-scheme: dark) {
  html {
    @apply bg-black text-white;
  }
}
Enter fullscreen mode Exit fullscreen mode

PostCSS and Tailwind configuration

PostCSS Config File

// postcss.config.js
module.exports = {
  plugins: [
    require("tailwindcss"),
    require("autoprefixer"),
    require("postcss-import"),
    process.env.NODE_ENV === "production" &&
      require("cssnano")({
        preset: "default",
      }),
  ],
};
Enter fullscreen mode Exit fullscreen mode

Tailwind Config File

// tailwind.config.js
module.exports = {
  mode: process.env.NODE_ENV ? "jit" : undefined,
  // To purge CSS in .ts .tsx files
  purge: ["./app/**/*.{ts,tsx}"], 
  darkMode: "media", // Use media queries for dark mode
  theme: {
    extend: {
      colors: {
        // color scheme is defined in /app.css
        // To enable text-primary-xxx, bg-primary-xxx, or border-primary-xxx
        primary: {
          light: "var(--color-primary-light)",
          100: "var(--color-primary-100)",
          200: "var(--color-primary-200)",
          300: "var(--color-primary-300)",
          400: "var(--color-primary-400)",
          500: "var(--color-primary-500)",
          600: "var(--color-primary-600)",
          700: "var(--color-primary-700)",
          800: "var(--color-primary-800)",
          900: "var(--color-primary-900)",
          dark: "var(--color-primary-dark)",
        },
      },
    },
  },
  variants: {}, // activate any variant you want here
  plugins: [], // add any plugin you need here
};
Enter fullscreen mode Exit fullscreen mode

Integrating styles in Remix Code

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

// app/root.js
// ...
import type { LinksFunction } from "remix";
import tailwindStyles from "~/styles/tailwind.css";
import appStyles from "~/styles/app.css";

export let links: LinksFunction = () => {
  return [
    { rel: "stylesheet", href: tailwindStyles },
    {
      rel: "stylesheet",
      href: appStyles,
    },
  ];
};
// ...
Enter fullscreen mode Exit fullscreen mode

Styling a component

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

//app/components/word-form/index.tsx
import { Form, useTransition } from "remix";
import { Word, WordType } from "~/models/word";
import { Button } from "../basic/button";
import { Input } from "../basic/input";
import { Select } from "../basic/select";
import { TextArea } from "../basic/textarea";

export function WordForm({ word }: { word?: Word }) {
  let transition = useTransition();

  return (
    <Form
      method="post"
      className={`
        px-3 py-4 rounded flex flex-col gap-2 border-2
      `}
    >
      <div>Form State: {transition.state}</div>
      <div>
        <label className="block text-xs" htmlFor="name">
          Word
        </label>
        <Input
          id="name"
          name="name"
          type="text"
          placeholder="Word"
          required
          defaultValue={word?.name ?? ""}
          disabled={Boolean(word?.name)}
        />
      </div>
      <div>
        <label className="block text-xs" htmlFor="type">
          Type
        </label>
        <Select
          id="type"
          name="type"
          defaultValue={word?.type ?? WordType.NOUN}
        >
          <option value={WordType.NOUN}>Noun</option>
          <option value={WordType.VERB}>Verb</option>
          <option value={WordType.ADJECTIVE}>Adjective</option>
        </Select>
      </div>
      {/*TextAreas*/}
      <Button type="submit" color="primary">
        Submit
      </Button>
    </Form>
  );
}
// ...
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (0)