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 cssnanoOR if you prefer yarn
yarn add -D autoprefixer postcss postcss-cli postcss-import tailwindcss cssnanoAdd scripts to package.json
Add Script for CSS generation
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 runwithyarnif you prefer to useyarn
I don't want to commit those generated CSS files to the repo, so I'll be adding them to .gitignore
app/styles/*.cssAdd Script for cleaning up build files
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
1npm run css:watch1npm run devDISCLAIMER: 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
1npm run buildIf you are not a fan of multiple terminals, use concurrently to run css:watch and remix dev in parallel
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.
1@tailwind base;2@tailwind components;3@tailwind utilities;4@tailwind screens;App CSS presets
Some CSS defaults I prefer
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
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
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
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.
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.
