Styling with Tailwind CSS v4
React on Rails can generate a Tailwind CSS v4 setup for the server-rendered
HelloWorld example. The generator uses Tailwind's CSS-first setup, the
@tailwindcss/postcss plugin, and the same shared bundler template for Webpack
and Rspack.
New Apps
For a fresh Rails app, pass --tailwind to create-react-on-rails-app:
npx create-react-on-rails-app my-app --tailwind
cd my-app
bin/rails db:prepare
bin/dev
The CLI forwards --tailwind to rails generate react_on_rails:install, so the
generated /hello_world page is server-rendered and styled by Tailwind CSS v4.
Existing Apps
For an existing Rails app, pass the generator flag directly:
bundle exec rails generate react_on_rails:install --tailwind
The generator installs these published packages:
pnpm add tailwindcss@^4.3.0 @tailwindcss/postcss@^4.3.0 postcss@^8.5.15 postcss-loader@^8.2.1
npm install tailwindcss@^4.3.0 @tailwindcss/postcss@^4.3.0 postcss@^8.5.15 postcss-loader@^8.2.1
yarn add tailwindcss@^4.3.0 @tailwindcss/postcss@^4.3.0 postcss@^8.5.15 postcss-loader@^8.2.1
It also creates app/javascript/stylesheets/application.css:
@import "tailwindcss" source("../..");
The generator also creates app/javascript/packs/react_on_rails_tailwind.js
to import the Tailwind stylesheet and declares that pack from
app/views/layouts/react_on_rails_default.html.erb:
<% prepend_javascript_pack_tag "react_on_rails_tailwind" %>
<%= stylesheet_pack_tag "react_on_rails_tailwind", media: "all" %>
<%= javascript_pack_tag %>
The generated HelloWorld.client.jsx or HelloWorld.client.tsx uses Tailwind
utility classes but does not import the global stylesheet. The shared
commonWebpackConfig.js template inserts postcss-loader after css-loader
and configures @tailwindcss/postcss, regardless of whether the project uses
Webpack or Rspack.
Server Rendering Without a Flash of Unstyled Content
The generated example keeps three pieces together:
app/views/hello_world/index.html.erbrenders the component withprerender: true.app/javascript/packs/react_on_rails_tailwind.jsimports the Tailwind stylesheet as an app-level pack.app/views/layouts/react_on_rails_default.html.erbkeeps<%= stylesheet_pack_tag "react_on_rails_tailwind", media: "all" %>before<%= javascript_pack_tag %>.
Tailwind is layout-owned because it is global app styling. The generated components only reference Tailwind class names. Do not move the Tailwind CSS import back into a generated component; pages that use Tailwind classes but do not load that component would then depend on the wrong pack for global styles.
In development, CSS can be injected by the dev server instead of extracted as a static link. Use a production build when validating the no-FOUC path.
Adding Tailwind Manually
If you are not rerunning the generator, add the packages yourself:
pnpm add tailwindcss@^4.3.0 @tailwindcss/postcss@^4.3.0 postcss@^8.5.15 postcss-loader@^8.2.1
npm install tailwindcss@^4.3.0 @tailwindcss/postcss@^4.3.0 postcss@^8.5.15 postcss-loader@^8.2.1
yarn add tailwindcss@^4.3.0 @tailwindcss/postcss@^4.3.0 postcss@^8.5.15 postcss-loader@^8.2.1
Then create a CSS entry, import it from an app-level client pack that your
layout declares, and add postcss-loader after css-loader in your shared
bundler config:
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [require('@tailwindcss/postcss')],
},
},
}
Keep the SSR view prerendered and keep the Tailwind stylesheet pack tag in the layout head so the app-level Tailwind CSS can be emitted before the JavaScript runs when Shakapacker is configured to emit stylesheet links.
Tailwind v3 projects should use Tailwind's v3 PostCSS setup instead of this v4 recipe.