Tailwind CSS Customisation — Tutorial Project Prompt
We are building a comprehensive standalone tutorial on customising Tailwind CSS in a Rails 8 application. This is a companion to a Phlex v2 on Rails tutorial series, but stands alone as a reference for any Rails developer working with Tailwind v4.
Context
This tutorial emerges from real decisions made building KanbanFlow — a Phlex-based Kanban application. The lessons are grounded in what actually matters in production: design tokens, dark mode, multi-theme support, and knowing when NOT to abstract.
The key insight driving the tutorial: Tailwind v4 is fundamentally different from v3. It is CSS-first, not config-first. Understanding what this means in practice — and what it changes about how you work with Tailwind in Rails — is the tutorial’s central thread.
Setup
- Rails 8, importmaps, Tailwind CSS v4, SQLite
- Phlex — components used throughout for clean, reusable examples
- Lookbook — the primary visual showcase for the token and theming system
- No Node.js, no webpack, no PostCSS config files
What this tutorial covers
Part 1 — Understanding Tailwind v4 in Rails
- How Tailwind v4 differs from v3 — CSS-first configuration, no
tailwind.config.js,@themedirective - How Rails 8 integrates Tailwind v4 — the build pipeline, where compiled CSS lives, how to inspect it
- How Tailwind v4 generates utilities on demand — why classes only appear in compiled CSS when they’re used, and what this means for development workflow
- The
@sourcedirective — ensuring Tailwind scans all your template files including non-standard locations
Part 2 — Design tokens with @theme
- What design tokens are and why they matter
- Defining colour tokens — semantic naming (
--color-primary) vs palette naming (--color-blue-600) - The case for semantic tokens: dark mode, multi-theme, rebranding
- What Tailwind v4 generates automatically from
@theme— colour utilities (bg-*,text-*,border-*) and what it doesn’t generate (radius, spacing) - Font family tokens —
--font-sans,--font-mono— the one non-colour token worth defining - What NOT to tokenise — radius, spacing, font sizes, font weights — and why Tailwind’s built-in utilities are sufficient
- The naming collision problem —
text-text,border-border— why it happens and whether it matters
Part 3 — Dark mode
- Two approaches:
@media (prefers-color-scheme: dark)vs.darkclass — when to use each - The clean approach:
@mediafor automatic,.darkfor manual override,:root:not(.light)to handle the three-way interaction - Complete dark mode token set — what values work well for dark surfaces, text, borders, and status colours
- The
color-schememeta tag — why it matters for native browser UI - Testing dark mode in development — OS preference, browser DevTools
Part 4 — Manual theme switching with Stimulus
- The
ThemeToggleStimulus controller — managinglocalStorage,.darkand.lightclasses on<html> - Handling the three states: explicit dark, explicit light, deferred to system preference
- The icon update pattern — reflecting current state accurately including system preference
Part 5 — Multi-theme support
- Defining a second colour theme with
[data-theme="forest"] - The
data-themeattribute on<html>— how it works with CSS specificity - Dark mode on top of a named theme —
[data-theme="forest"].dark { } - Extending the Stimulus controller to cycle through themes
- The key insight: components are theme-agnostic by design — they reference semantic tokens, themes provide the values
Part 6 — Lookbook as a theming showcase
This is a key section. Lookbook becomes the living proof that the token system works — every component is visible in one place, and switching themes shows everything repaint in real time.
- Setting up Lookbook with the token CSS in the preview layout
- The challenge: Lookbook previews render in iframes — the parent page’s theme toggle doesn’t automatically affect them
- Adding theme switching controls to the Lookbook preview layout —
a dedicated
ThemeToggleinapp/views/layouts/lookbook/preview.html.erb - Using Lookbook’s
@displayannotation to set per-preview display options — background colour, padding, theme context - Exploring whether
data-themeon the preview body affects components rendered inside — investigation and findings - Writing previews that demonstrate theme-awareness — showing the same component in multiple theme contexts side by side
- The component gallery in Lookbook — all components, all variants, switchable between light/dark and named themes
Part 7 — Typography
- Loading custom fonts — Google Fonts, local fonts, system fonts
- The
--font-sansand--font-monotokens - When to use Tailwind’s type scale vs defining custom sizes
- Prose content with
@tailwindcss/typography
Part 8 — Practical patterns
- Extracting repeated class combinations —
@applyvs components vs just accepting repetition - When to use
@applyand when not to — the tradeoffs - Component-scoped styles — how Phlex components affect where styles live
- CSS custom properties vs Tailwind tokens — when plain CSS variables are the better tool
- Handling third-party component styles — overriding without fighting specificity
Part 9 — Production considerations
- Ensuring all dynamic classes are included — safelist,
@source - Asset fingerprinting and caching in Rails
- CSS bundle size — what’s realistic, what to do if it’s large
- CDN vs self-hosted fonts — tradeoffs
Showcase project
Build a simple Rails app with Phlex components and Lookbook as the primary showcase:
Lookbook component gallery — every UI component (buttons, badges, cards, forms, alerts, inputs) with all variants, viewable in:
- Light mode
- Dark mode
- Forest theme (light)
- Forest theme (dark)
The gallery is the living proof that the token system works — switch the theme in Lookbook and every component repaints with zero component code changes.
Three demo pages in the app itself:
- Component page — same components as Lookbook, accessible in the running app to verify theming works outside the preview context
- Typography page — headings, body text, prose content, code blocks
- Form page — all form primitives styled consistently, error states, all variants
Key questions to investigate and answer
- Does Lookbook’s iframe inherit
data-themeand.darkfrom the parent page, or is it isolated? - What is the cleanest way to add theme switching to Lookbook previews
— preview layout,
@displayannotations, or something else? - Can
@displayoptions inject HTML attributes onto the preview wrapper element? - Is there a Lookbook community convention for theme-aware previews?
These questions should be investigated by actually building the project and reporting findings honestly — including dead ends.
Key teaching principles
- Show what doesn’t work first — raw palette values, then the problem they cause, then tokens as the solution
- Earn every abstraction — only introduce a concept when there’s a concrete problem it solves
- Be honest about tradeoffs —
@applyhas real costs, token naming has real awkwardness, sometimes plain classes are better - Tailwind v4 specific — don’t teach v3 patterns that don’t apply
- Lookbook as proof — the component gallery in Lookbook is the most compelling demonstration of what the token system achieves
Relationship to main tutorial
This tutorial is a companion to the Phlex v2 on Rails series. Module 7 of that series covers design tokens, dark mode, and multi-theme support at a pace appropriate for a broader tutorial. This standalone tutorial goes deeper on every topic — it’s for developers who want to understand Tailwind customisation thoroughly rather than follow along with a specific project.
References back to the Phlex series where relevant (e.g. “in KanbanFlow we made this decision because…”) but is otherwise self-contained.