Dark Mode

Add beautiful dark mode support to your Rails application with RapidRails UI's built-in theme system.

How Dark Mode Works

RapidRails UI uses a smart dark mode system that respects your OS preferences while giving you full control. It combines prefers-color-scheme detection with data-theme attributes for the best of both worlds.

Smart Theme Detection

The theme system automatically:

  • Detects your OS theme using prefers-color-scheme on first visit
  • Watches for OS changes and updates automatically if you haven't set a preference
  • Remembers your choice in localStorage when you manually toggle the theme
  • Allows override so you can use a different theme than your OS setting

Best of both worlds:

If you never click the theme toggle, the site automatically matches your OS theme and updates when you change it. If you manually toggle the theme, your preference is saved and respected regardless of OS changes.

How It Works Technically

RapidRails UI configures Tailwind CSS v4 to use a data-theme attribute via a custom variant in app/assets/tailwind/application.css:

@import "tailwindcss";
/**
 * Dark Mode Configuration (Tailwind v4)
 * Uses data-theme attribute instead of class
 * See: https://tailwindcss.com/docs/dark-mode
 */
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));

The Stimulus controller then sets the data-theme attribute on the HTML element, which triggers Tailwind's dark: classes:

<html data-theme="dark" style="color-scheme: light;">
  <!-- All dark: classes now activate -->
  <button class="bg-white dark:bg-zinc-900">
    Button
  </button>
</html>

When data-theme="dark" is set, Tailwind's dark: variant becomes active, applying all dark mode styles automatically.

Automatic Setup (Recommended)

If you ran the RapidRails UI installer, dark mode is already configured for you:

rails generate rapid_rails_ui:install

The installer automatically sets up:

Tailwind CSS v4 dark mode configuration in app/assets/tailwind/application.css
Theme switcher component in app/views/shared/_dark_mode_switcher.html.erb
Stimulus controller in app/javascript/controllers/theme_controller.js
LocalStorage persistence so theme choice persists across sessions

Manual Setup

If you prefer to set up dark mode manually or customize the installer's defaults, follow these steps:

Step 1: Configure Tailwind CSS

Add the dark mode custom variant to your Tailwind CSS file:

// app/assets/tailwind/application.css
@import "tailwindcss";

@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));

Step 2: Add Theme Toggle

Create a simple theme switcher using Stimulus:

// app/javascript/controllers/theme_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    // Load saved theme or default to light
    const theme = localStorage.getItem("theme") || "light"
    this.setTheme(theme)
  }

  toggle() {
    const current = document.documentElement.getAttribute("data-theme")
    const next = current === "dark" ? "light" : "dark"
    this.setTheme(next)
  }

  setTheme(theme) {
    document.documentElement.setAttribute("data-theme", theme)
    localStorage.setItem("theme", theme)
  }
}

Step 3: Add Toggle Button

Add a button in your layout to toggle the theme:

// app/views/layouts/application.html.erb
<div data-controller="theme">
  <button
    data-action="click->theme#toggle"
    class="p-2 rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-800"
  >
    Toggle Dark Mode
  </button>
</div>

Using the Theme Switcher

If you used the installer, RapidRails UI provides a ready-to-use theme switcher component with sun/moon icons that automatically toggles between light and dark modes.

Basic Usage

Add the switcher anywhere in your layout:

// app/views/layouts/application.html.erb
<header>
  <nav>
    <!-- Your navigation -->
    <%= render 'shared/dark_mode_switcher' %>
  </nav>
</header>

The switcher will display:

  • A sun icon in dark mode (click to switch to light)
  • A moon icon in light mode (click to switch to dark)
  • Smooth transitions between themes
  • Persisted preference in localStorage

Customizing Dark Mode Colors

All RapidRails UI components use Tailwind's dark: variant with semantic color choices. You can customize dark mode colors in two ways:

Option 1: Per-Component Override

Override colors using the class option. When fully customizing colors, omit the color parameter to avoid conflicts:

<%= rui_button(
  "Custom Dark Mode",
  class: "bg-pink-600 dark:bg-purple-600 hover:bg-pink-700 dark:hover:bg-purple-700 text-white"
) %>

Option 2: Global Configuration

Customize default dark mode colors in your RapidRails UI initializer:

# config/initializers/rapid_rails_ui.rb
RapidRailsUI.configure do |config|
  config.colors[:primary] = {
    base: "blue-600",
    hover: "blue-700",
    active: "blue-800",
    text: "white",
    dark: {
      base: "purple-500",      # Custom dark mode primary
      hover: "purple-600",
      active: "purple-700",
      text: "white"
    }
  }
end

Note: Global color customization is an advanced feature. Most apps won't need it, the default dark mode colors are carefully chosen for accessibility and aesthetics.

Testing Dark Mode

Verify your dark mode setup works correctly:

Visual Test

  1. Start your Rails server and open your application
  2. Click the theme switcher (sun/moon icon)
  3. Verify the page switches between light and dark themes
  4. Refresh the page, theme should persist
  5. Inspect the <html> element, should have data-theme="dark" or data-theme="light"

Component Test

Add a test component to any view:

<div class="p-8 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800">
  <h2 class="text-2xl font-bold text-zinc-900 dark:text-white mb-4">
    Dark Mode Test
  </h2>
  <p class="text-zinc-600 dark:text-zinc-400 mb-4">
    This text should change color in dark mode.
  </p>
  <%= rui_button("Primary Button", color: :primary) %>
  <%= rui_button("Danger Button", color: :danger, variant: :outline) %>
</div>

Dark Mode Test

This text should change color in dark mode.

Toggle dark mode, all elements should adapt their colors automatically.

Success! If all colors adapt correctly when toggling, your dark mode is working perfectly.