Use class_names to Conditionally Apply CSS Classes
When you’re building views in Rails, you often need to apply CSS classes conditionally. Maybe a nav link should look different when it’s the current page, or a form field needs error styling. Since Rails 6.1, the class_names helper does this cleanly.
Instead of…
…interpolating conditional classes with ternaries or post-statement conditionals:
<div class="p-4 rounded <%= @error ? 'bg-red-50 border-red-500' : '' %> <%= 'opacity-50 cursor-not-allowed' if @disabled %>">
<%= @message %>
</div>
Use…
…the class_names helper:
<%= tag.div class: class_names(
"p-4 rounded",
"bg-red-50 border-red-500": @error,
"opacity-50 cursor-not-allowed": @disabled
) do %>
<%= @message %>
<% end %>
String arguments (like "p-4 rounded") are always applied. The trailing Hash (the keyword-style arguments) includes any key whose value is truthy and silently drops the rest.
An active nav link:
<%= link_to "Home", root_path,
class: class_names("nav-link px-3 py-2",
"text-blue-700 font-semibold": current_page?(root_path)) %>
A form field with errors:
<%= f.text_field :email,
class: class_names("field",
"field--error": @user.errors[:email].any?) %>
A flash message:
<% flash.each do |type, message| %>
<%= tag.p message, class: class_names("flash",
notice: type == "notice",
alert: type == "alert") %>
<% end %>
An active tab:
<%= link_to "Overview", project_path(@project),
class: class_names("tab", "tab--active": current_page?(project_path(@project))) %>
Or wrap a repeated pattern in a helper:
def class_names_for_project(project)
class_names("status-badge",
"status-badge--primary": project.active?,
"status-badge--muted": project.archived?)
end
<%= tag.span @project.status, class: class_names_for_project(@project) %>
Why?
The unsophisticated approach ends up with extra whitespace in the rendered HTML with ERB tags inside an HTML attribute (which I’m not a fan of visually), plus it’s hard to scan which classes are always present and which are conditional.
class_names returns an HTML-safe string, so you don’t need to worry about escaping. It also splits whitespace-separated tokens and deduplicates them, so class_names("p-4", "p-4 rounded") collapses to "p-4 rounded" rather than repeating p-4.
It’s available in any view or helper in Rails, since it’s defined in ActionView::Helpers::TagHelper. You might also see it referred to as token_list, which is the original method name.
Why not?
If you’ve only got a single conditional class, plain ERB is readable enough:
<div class="p-4 <%= 'font-bold' if @important %>">
Though class_names does avoid the awkward whitespace issue when the condition is false:
<%= tag.div class: class_names("p-4", "font-bold": @important) do %>
Published on May 11th, 2026