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 installpnpm create giojs my-app --tailwind # a new app
pnpm dlx create-giojs add tailwind # an existing app
pnpm installyarn create giojs my-app --tailwind # a new app
yarn dlx create-giojs add tailwind # an existing app
yarnbun create giojs my-app --tailwind # a new app
bunx create-giojs add tailwind # an existing app
bun installGioJS 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/cliandtailwindcssas dependencies (not dev dependencies:startbuilds the stylesheet, and a deploy from source installs withnpm ci --omit=dev), and these scripts:
{
"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.
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.
@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.