The fastest, framework-agnostic conditional CSS class builder for Ruby.
Perfect for ViewComponent, Phlex, Tailwind CSS or just standalone.
Inspired by the JavaScript clsx package. Works with any Ruby codebase.
Quick Start
bundle add clsx-rubyOr add it manually to the Gemfile:
gem 'clsx-ruby', '~> 1.1'Then use it:
require 'clsx'
Clsx['btn', 'btn-primary', active: is_active, disabled: is_disabled]
# => "btn btn-primary active" (when is_active is truthy, is_disabled is falsy)Rails Integration
For Rails integration (adds clsx and cn helpers to all views), see clsx-rails.
Why clsx-ruby?
Blazing fast
2-3x faster than Rails class_names across every scenario:
| Scenario | clsx-ruby | Rails class_names
|
Speedup |
|---|---|---|---|
| String array | 1.3M i/s | 369K i/s | 3.4x |
| Multiple strings | 1.3M i/s | 408K i/s | 3.3x |
| Single string | 2.4M i/s | 893K i/s | 2.7x |
| Mixed types | 912K i/s | 358K i/s | 2.5x |
| Hash | 1.7M i/s | 672K i/s | 2.5x |
| String + hash | 1.2M i/s | 565K i/s | 2.1x |
Ruby 4.0.1, Apple M1 Pro. Reproduce: bundle exec ruby benchmark/run.rb
More feature-rich than class_names
| Feature | clsx-ruby | Rails class_names
|
|---|---|---|
| Conditional classes | ✅ | ✅ |
| Auto-deduplication | ✅ | ✅ |
| 2–3× faster | ✅ | ❌ |
Returns nil when empty |
✅ | ❌ (returns "") |
| Complex hash keys | ✅ | ❌ |
| Framework-agnostic | ✅ | ❌ |
| Zero dependencies | ✅ | ❌ |
Tiny footprint
~100 lines of code. Zero runtime dependencies. Ruby 3.2+.
Usage
Bracket API (recommended)
require 'clsx'
Clsx['foo', 'bar']
# => 'foo bar'
Clsx['foo', bar: true, baz: false]
# => 'foo bar'
Clsx['btn', 'btn-primary', active: is_active, disabled: is_disabled]
# => 'btn btn-primary active' (when is_active is truthy, is_disabled is falsy)Short alias
Cn['foo', bar: true]
# => 'foo bar'Cn is defined only if the constant is not already taken.
Mixin
include Clsx::Helper
clsx('foo', 'bar')
# => 'foo bar'
cn(hidden: @hidden, 'text-bold': @bold)
# => 'hidden text-bold' (when both are truthy)Module methods
Clsx.clsx('foo', 'bar')
# => 'foo bar'
Clsx.cn('foo', bar: true)
# => 'foo bar'Input types
# Strings (variadic)
Clsx['foo', true && 'bar', 'baz']
# => 'foo bar baz'
# Hashes
Clsx[foo: true, bar: false, baz: a_truthy_method]
# => 'foo baz'
# Hashes (variadic)
Clsx[{ foo: true }, { bar: false }, nil, { '--foobar': 'hello' }]
# => 'foo --foobar'
# Arrays
Clsx[['foo', nil, false, 'bar']]
# => 'foo bar'
# Arrays (variadic)
Clsx[['foo'], ['', nil, false, 'bar'], [['baz', [['hello'], 'there']]]]
# => 'foo bar baz hello there'
# Symbols
Clsx[:foo, :'bar-baz']
# => 'foo bar-baz'
# Numbers
Clsx[1, 2, 3]
# => '1 2 3'
# Kitchen sink (with nesting)
Clsx['foo', ['bar', { baz: false, bat: nil }, ['hello', ['world']]], 'cya']
# => 'foo bar hello world cya'Framework Examples
Rails
<%= tag.div class: Clsx['foo', 'baz', 'is-active': @active] do %>
Hello, world!
<% end %>Sinatra
erb :"<div class='#{Clsx['nav', active: @active]}'>...</div>"ViewComponent
class AlertComponent < ViewComponent::Base
include Clsx::Helper
def initialize(variant: :info, dismissible: false)
@variant = variant
@dismissible = dismissible
end
def classes
clsx("alert", "alert-#{@variant}", dismissible: @dismissible)
end
end<div class="<%= classes %>">...</div>Tailwind CSS
class NavLink < ViewComponent::Base
include Clsx::Helper
def initialize(active: false)
@active = active
end
def classes
clsx(
'px-3 py-2 rounded-md text-sm font-medium transition-colors',
'text-white bg-indigo-600': @active,
'text-gray-300 hover:text-white hover:bg-gray-700': !@active
)
end
endPhlex
class Badge < Phlex::HTML
include Clsx::Helper
def initialize(color: :blue, pill: false)
@color = color
@pill = pill
end
def view_template
span(class: clsx("badge", "badge-#{@color}", pill: @pill)) { yield }
end
endDifferences from JavaScript clsx
-
Returns
nilwhen no classes apply (not an empty string). This prevents rendering emptyclass=""attributes in template engines that skipnil:Clsx[nil, false] # => nil
-
Deduplication — Duplicate classes are automatically removed, even across multi-token strings:
Clsx['foo', 'foo'] # => 'foo' Clsx['foo bar', 'foo'] # => 'foo bar'
-
Falsy values — In Ruby only
falseandnilare falsy, so0,'',[],{}are all truthy:Clsx['foo' => 0, bar: []] # => 'foo bar'
-
Complex hash keys — Any valid
clsxinput works as a hash key:Clsx[[{ foo: true }, 'bar'] => true] # => 'foo bar'
-
Ignored values — Boolean
trueandProc/lambda objects are silently ignored:Clsx['', proc {}, -> {}, nil, false, true] # => nil
Development
bin/setup # install dependencies
bundle exec rake test # run tests
bundle exec ruby benchmark/run.rb # run benchmarksContributing
Bug reports and pull requests are welcome on GitHub at https://github.com/svyatov/clsx-ruby.
License
The gem is available as open source under the terms of the MIT License.