12
Styling Remix using Tailwind and PostCSS
TL;DR: Source and Demo
Here's a live demo
Link to the source code
Link to step by step commits
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.
npm install -D autoprefixer postcss postcss-cli postcss-import tailwindcss cssnano
OR if you prefer yarn
yarn add -D autoprefixer postcss postcss-cli postcss-import tailwindcss cssnano
// 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",
// ...
},
Replace
npm run
withyarn
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
// package.json
"scripts": {
// ...
"build": "npm run css:prod && remix build",
"prebuild": "rimraf ./public/build \"./app/styles/**/*.css\""
// ...
},
- Development
Run npm run css:watch
in one terminal and remix dev
in another
npm run css:watch
npm run dev
DISCLAIMER: Don't expect it will work immediately. We still need to configure a few things with Tailwind and PostCSS.
- Production
npm run build
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",
// ...
}
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;
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;
}
}
// postcss.config.js
module.exports = {
plugins: [
require("tailwindcss"),
require("autoprefixer"),
require("postcss-import"),
process.env.NODE_ENV === "production" &&
require("cssnano")({
preset: "default",
}),
],
};
// 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
};
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,
},
];
};
// ...
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>
);
}
// ...
If you're wondering where the above file came from, that is from my last blog post.
Here are some plugins that you can use to get a better experience using Tailwind and PostCSS in VSCode.
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.
12