GioJSdocs
On this page

Tailwind CSS

Tailwind v4 through its official CLI: one generated stylesheet, rebuilt as you edit and bundled by GioJS like any other imported CSS.

Set it up

npm create giojs@latest my-app -- --tailwind   # a new app
npx create-giojs add tailwind                    # an existing app
npm install

GioJS does not compile Tailwind directives itself (see CSS & Styling). The feature sets up the recipe from that page:

  • app/tailwind.css, the input: @import "tailwindcss"; plus your own theme and rules.
  • app/tailwind.out.css, the output the CLI writes. The root layout imports it (import './tailwind.out.css';), so GioJS bundles it into the page stylesheets. It is generated, so it is git-ignored.
  • @tailwindcss/cli and tailwindcss as dependencies (not dev dependencies: start builds the stylesheet, and a deploy from source installs with npm ci --omit=dev), and these scripts:
json
{
  "dev": "node scripts/dev.mjs",
  "dev:server": "cross-env NODE_ENV=development giojs-server",
  "css:build": "tailwindcss -i ./app/tailwind.css -o ./app/tailwind.out.css --minify",
  "css:watch": "tailwindcss -i ./app/tailwind.css -o ./app/tailwind.out.css --watch",
  "build": "tailwindcss -i ./app/tailwind.css -o ./app/tailwind.out.css --minify && tsc --noEmit",
  "start": "tailwindcss -i ./app/tailwind.css -o ./app/tailwind.out.css --minify && cross-env NODE_ENV=production giojs-server"
}

Development

npm run dev runs scripts/dev.mjs, a small runner with no dependencies: it starts the Tailwind watcher, waits for its first build, then starts the GioJS dev server. When either stops, it stops the other. Add a class to any component and the watcher rewrites the output; the dev server picks that up and reloads the browser.

Start development with npm run dev, not gio dev (or npx gio dev): those start only the GioJS server, so in a fresh clone app/tailwind.out.css - git-ignored - does not exist yet, and nothing rebuilds it as you edit. Run npm run css:build once if you start the server another way.

tsx
export default function Hero(): React.JSX.Element {
  return <h1 className="text-4xl font-semibold tracking-tight text-orange-500">Hello</h1>;
}

Tailwind finds class names by scanning the project's source files and skips what .gitignore lists (node_modules, .gio, the output itself). Class names must appear whole in the source - build them from a lookup object, not by string concatenation.

The starter's own styles

The starter's stylesheet, app/globals.css, is no longer imported by the root layout: the feature replaces that import './globals.css'; with the generated stylesheet's import, and app/tailwind.css imports it into Tailwind's base layer instead - so the starter pages keep their look, and its gio-* classes keep working next to the utilities.

css
@import "tailwindcss";
@import "./globals.css" layer(base);

A server app's fonts still come from [[fonts]] in gio.toml; a static site's @font-face rules live in globals.css and come along with it. A layout that imports no globals.css just gets the generated stylesheet's import.

That matters because of cascade layers: a rule outside any layer beats every layered rule, whatever its specificity. Left unlayered, the starter's resets (such as * { padding: 0 }) would override utilities like p-4. Inside base, every utility wins. Drop the import once you have replaced the starter styles.

Builds and deploys

npm run build, npm start and (for a static site) gio export through npm run build run a minified one-off build first, so the output is never stale. The Docker and CI features run npm run build before gio build standalone for the same reason, and a project without a build script (an app from create-giojs migrate) gets one that builds the stylesheet: the output is git-ignored, so a clean checkout has to build it.