Skip to content

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, @theme directive
  • 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 @source directive — 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 .dark class — when to use each
  • The clean approach: @media for automatic, .dark for 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-scheme meta 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 ThemeToggle Stimulus controller — managing localStorage, .dark and .light classes 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-theme attribute 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 ThemeToggle in app/views/layouts/lookbook/preview.html.erb
  • Using Lookbook’s @display annotation to set per-preview display options — background colour, padding, theme context
  • Exploring whether data-theme on 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-sans and --font-mono tokens
  • When to use Tailwind’s type scale vs defining custom sizes
  • Prose content with @tailwindcss/typography

Part 8 — Practical patterns

  • Extracting repeated class combinations — @apply vs components vs just accepting repetition
  • When to use @apply and 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:

  1. Component page — same components as Lookbook, accessible in the running app to verify theming works outside the preview context
  2. Typography page — headings, body text, prose content, code blocks
  3. Form page — all form primitives styled consistently, error states, all variants

Key questions to investigate and answer

  • Does Lookbook’s iframe inherit data-theme and .dark from the parent page, or is it isolated?
  • What is the cleanest way to add theme switching to Lookbook previews — preview layout, @display annotations, or something else?
  • Can @display options 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@apply has 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.