A helper library for managing tailwind class lists. Intended for use in component based rails applications. The examples here use view_component, but this should be applicable to any component system.
A common issue that you encounter using tailwind in a component based application is class conflicts. Say you have a component "Box" that has a default margin and you allow passing in classes that will be applied to your component. Someone passes in a margin class. What happens?
class BoxComponent < ViewComponent::Base
def initialize(classes:)
@classes = classes
end
end
# <div class="m-4 <%= @classes %">><%= content %></div>
Your user passes in "m-8":
<%= render BoxComponent.new(classes: "m-8") do %>
<p>My content</p>
<% end %>
This results in the following html
<div class="m-4 m-8">
<p>My content</p>
</div>
That's a good question. What happens today is that both the .m-4
and .m-8
css rules are applied to the element and the one with the highest specificity "wins". The rule with the highest specificity in this case is .m-8
and thus the element has a 2rem margin - the margin applied by the .m-8
rule.
If we pass in a m-0
class, the .m-4
rule has the highest specificity and that rule is applied.
Why does the m-8
class win when it's passed in, but m-0
not? This is because the .m-8
rule is defined after the .m-4
rule, which is defined after the .m-0
rule.
This behavior is described in a github issue here: tailwindlabs/tailwindcss#1010
- The order of css rule definitions is opaque to developers.
- Conflicts fail silently.
- Unnecessary classes make it past code review
- Developers have to root out the conflict themselves.
- Silent failures and unexpected effects make this behavior unfriendly to component based applications.
In component based applications, we often want to allow developers to merge style lists. We want to be able to support two behaviors:
- Merge and raise errors about conflicts (e.g. when we pass in the
m-0
class we get an error telling us that it conflicts with them-4
class.) - Merge and override the style (e.g. when we pass in the
m-0
class, we want to remove them-4
class.)
Side note, in Tailwind play, a warning will allert you to the cssConflict in the div above. I wish there was a tool to surface this via static analysis. (Although this is tricky b/c whatever tool you use has to understand your component system.)
Add this line to your application's Gemfile:
gem 'exhaust_pipe'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install exhaust_pipe
include ExhaustPipe
tailwind("bg-red-100", "m-10")
# => ExhaustPipe::TokenList("bg-red-100 m-10")
classes = tailwind("bg-red-100", "m-10")
# => ExhaustPipe::TokenList("bg-red-100 m-10")
classes.add("w-10")
# => ExhaustPipe::TokenList("bg-red-100 m-10 w-10")
If you add a class that will conflict with another class in the list it will raise an error (this is configurable
tailwind("bg-red-100", "m-10").add("m-12")
# => ExhaustPipe::TokenConflictError
Sometimes, you will have a defined list of base styles (say in a component) but you'll want the user to be able to override a given style.
tailwind("bg-red-100", "m-10").override("m-12")
# => ExhaustPipe::TokenList("bg-red-100 m-12")
TODO
In certain environments (like production), you may want to turn off raising errors because a TokenConflictError is not important enough to raise.
ExhaustPipe.raise_errors = false
- Benchmark
- Allow adding custom classes
- Support all default tailwind classes
- Add a section describing usage with the view component library
After checking out the repo, run bin/setup
to install dependencies. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/RowanMcDonald/exhaust_pipe. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the ExhaustPipe project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.