Skip to content
ExponaBLOG
RUBY ON RAILS · DAY 057 / 100

Tailwind CSS with Rails: a practical Ruby on Rails guide

Utility-first styling.

Expona Admin7 min read

After months of building Rails applications, you've probably noticed that inline styles and custom CSS files can quickly become unwieldy as your codebase grows. Tailwind CSS with Rails: a practical Ruby on Rails guide shows us how to leverage utility-first styling to write cleaner, more maintainable CSS without leaving your markup. In this article, we'll walk through setting up Tailwind CSS in a modern Rails application and integrate it into your development workflow so your team can ship styled components faster.

Why Tailwind CSS in Rails?

Tailwind CSS has become the go-to choice for Rails teams because it eliminates the context-switching between CSS files and view templates. Traditional CSS requires you to name classes, maintain separate stylesheets, and manage specificity issues. With Tailwind's utility-first approach, you compose styles directly in your HTML using small, single-purpose classes like flex, px-4, and text-lg.

For production Rails applications handling thousands of requests, this approach reduces your final CSS bundle size dramatically. Tailwind ships with a built-in purge mechanism that removes unused classes during the build process, meaning your production stylesheet only contains the utilities your views actually reference.

The tailwindcss-rails Gem

Rails 7+ includes official integration through the tailwindcss-rails gem. When you generate a new Rails app, you can scaffold Tailwind support directly:

ruby
rails new myapp --css tailwind

For existing applications, add the gem to your Gemfile:

ruby
bundle add tailwindcss-rails

Then run the installation generator:

ruby
rails tailwindcss:install

This generator creates three essential files:

  • app/assets/stylesheets/application.tailwind.css – Your entry point where you import Tailwind directives
  • config/tailwind.config.js – Configuration for customizing colors, spacing, fonts, and extensions
  • Procfile.dev – Process management for development

Your application.tailwind.css will look minimal by default:

ruby
@tailwind base;
@tailwind components;
@tailwind utilities;

These three directives inject Tailwind's base styles, component utilities (like buttons), and all utility classes into your stylesheet. The gem handles the compilation pipeline automatically, so you don't need to configure Webpack, esbuild, or PostCSS manually unless you want custom behavior.

Watch Mode with bin/dev

During development, you need Tailwind's CSS compiler and your Rails server running simultaneously. The tailwindcss-rails gem integrates with Rails' bin/dev command to orchestrate both processes.

Start development with:

ruby
./bin/dev

This reads the Procfile.dev and spins up two processes: the Tailwind CSS compiler in watch mode and the Rails server on port 3000. When you modify any template containing Tailwind classes, the compiler detects the change and regenerates your CSS within milliseconds.

We recommend keeping a terminal window dedicated to ./bin/dev during active development. If you need to run additional services—database migrations, background jobs, or external APIs—add them to Procfile.dev:

ruby
web: bundle exec rails server -p 3000
css: bundle exec rails tailwindcss:watch
worker: bundle exec sidekiq

Each line represents a separate process that foreman (or similar process manager) will start and supervise. When you hit Ctrl+C, all processes terminate cleanly.

Tailwind CSS with View Components

View Components provide excellent structure for reusable UI elements, and pairing them with Tailwind CSS creates a powerful component system. Instead of scattering Tailwind classes across many templates, you encapsulate styles and markup together.

Let's build a reusable button component. First, generate the component:

ruby
rails generate view_component Button

This creates app/components/button_component.rb and app/components/button_component.html.erb. Define the component logic:

ruby
# app/components/button_component.rb
class ButtonComponent < ViewComponent::Base
  def initialize(text:, variant: :primary, size: :md, disabled: false)
    @text = text
    @variant = variant
    @size = size
    @disabled = disabled
  end

  private

  def base_classes
    "inline-flex items-center justify-center font-semibold transition-colors rounded-lg"
  end

  def variant_classes
    case @variant
    when :primary
      "bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800"
    when :secondary
      "bg-gray-200 text-gray-900 hover:bg-gray-300 active:bg-gray-400"
    when :danger
      "bg-red-600 text-white hover:bg-red-700 active:bg-red-800"
    end
  end

  def size_classes
    case @size
    when :sm
      "px-3 py-1 text-sm"
    when :md
      "px-4 py-2 text-base"
    when :lg
      "px-6 py-3 text-lg"
    end
  end

  def disabled_classes
    @disabled ? "opacity-50 cursor-not-allowed" : ""
  end

  def css_classes
    [base_classes, variant_classes, size_classes, disabled_classes].join(" ")
  end
end

Now create the corresponding template:

ruby
<button class="<%= css_classes %>" <%= "disabled" if @disabled %>>
  <%= @text %>
</button>

Use the component throughout your views with clean, semantic calls:

ruby
<%= render ButtonComponent.new(text: "Save Changes", variant: :primary, size: :lg) %>
<%= render ButtonComponent.new(text: "Cancel", variant: :secondary, size: :md) %>
<%= render ButtonComponent.new(text: "Delete", variant: :danger, size: :md, disabled: true) %>

This approach centralizes your styling logic, making it easier to maintain consistency across your application. When the design team requests a button style update, you modify one component rather than hunting through dozens of templates.

Purging Unused Classes in Production

Tailwind's real power emerges when you build for production. By default, Tailwind includes every utility class in your CSS—potentially 200+ kilobytes. In production, we purge unused classes to dramatically reduce bundle size.

The tailwindcss-rails gem automatically configures purging in your config/tailwind.config.js:

ruby
module.exports = {
  content: [
    './app/**/*.{erb,haml,html,slim}',
    './app/components/**/*.{erb,haml,html,slim}',
    './app/helpers/**/*.rb',
    './app/javascript/**/*.js',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

The content array tells Tailwind which files to scan for class references. Tailwind parses these files, identifies all class names, and includes only those utilities in the production build.

This scanning is smart but literal. If you dynamically generate class names at runtime (for example, building strings in JavaScript), Tailwind won't detect them. Always reference classes as complete strings in your source code:

ruby
# Good – Tailwind detects "text-red-500"
<div class="text-red-500">Error message</div>

# Bad – Tailwind misses this because the class is partial
<div class="<%= "text-#{color}-500" %>">Message</div>

# Good – Use object notation with Tailwind
<div class="<%= { 'text-red-500': error?, 'text-green-500': success? } %>">Message</div>

When you run rails assets:precompile for production deployment, Tailwind automatically purges unused classes. Typical production stylesheets drop from 200KB to 20-40KB, significantly improving page load times for your users.

Customizing Your Tailwind Configuration

Every product has unique design requirements. Tailwind's configuration system lets you extend the default theme with custom colors, spacing scales, and typography settings.

Suppose your brand uses a custom color palette. Edit config/tailwind.config.js:

ruby
module.exports = {
  content: [
    './app/**/*.{erb,haml,html,slim}',
    './app/components/**/*.{erb,haml,html,slim}',
  ],
  theme: {
    extend: {
      colors: {
        'brand-blue': '#0066cc',
        'brand-green': '#00aa44',
        'brand-gray': '#f5f5f5',
      },
      spacing: {
        '128': '32rem',
        '144': '36rem',
      },
      fontSize: {
        'xs': ['0.75rem', { lineHeight: '1rem' }],
        '2xl': ['1.5rem', { lineHeight: '2rem' }],
      },
    },
  },
  plugins: [],
}

Now use these custom utilities in your templates:

ruby
<div class="bg-brand-blue text-white px-4 py-2 rounded-lg">
  <h2 class="text-2xl font-bold">Welcome</h2>
  <p class="mt-2">This uses our custom brand colors.</p>
</div>

For plugins, Tailwind offers first-party packages like @tailwindcss/forms for styling form inputs. Install and register them:

ruby
npm install -D @tailwindcss/forms

Then add to your config:

ruby
module.exports = {
  // ... content and theme config
  plugins: [
    require('@tailwindcss/forms'),
  ],
}

Common Mistakes

Dynamically generating class names: Avoid string interpolation for class names. Tailwind's purge can't detect partial class references. Use conditional logic or helper methods instead.

Forgetting to run bin/dev during development: If you run Rails server separately without the Tailwind watcher, CSS changes won't compile. Always use ./bin/dev for local development.

Over-customizing the theme: Resist the urge to customize every value. The default Tailwind palette and spacing are carefully designed and battle-tested. Extend only what truly differs from brand guidelines.

Including third-party CSS before Tailwind directives: If you need to import external stylesheets, do so after @tailwind utilities; to avoid specificity issues. Better yet, find Tailwind plugins or rewrite third-party styles using utilities.

Neglecting safelist for dynamic classes: If you have legitimate dynamic class generation, add those classes to Tailwind's safelist in the config so they're never purged:

ruby
module.exports = {
  content: ['./app/**/*.{erb,haml,html,slim}'],
  safelist: [
    'text-red-500',
    'text-blue-500',
    'bg-red-100',
    'bg-blue-100',
  ],
  theme: { extend: {} },
  plugins: [],
}

Key Takeaways

  • The tailwindcss-rails gem provides zero-config Tailwind integration; use rails new --css tailwind or bundle add tailwindcss-rails with the installer
  • Always develop with ./bin/dev to run both Rails and the Tailwind CSS compiler in watch mode
  • Encapsulate Tailwind classes in View Components for reusable, maintainable UI elements with consistent styling
  • Production builds automatically purge unused classes; keep class names as complete strings in your source code so Tailwind detects them correctly
  • Customize your Tailwind config's theme and plugins to match your product's design system without overengineering
  • Test purging locally with TAILWIND_MODE=build rails assets:precompile to catch dynamic class issues before deployment
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles