DEV Community

Vivek Raja
Vivek Raja

Posted on

Better HAML for Tailwind CSS

The Problem

When using Tailwind CSS, I didn't like the fact that classes for different states and different breakpoints (such as hover, focus, sm, etc) were grouped together in one class attribute in a HTML element. It made it hard to figure out which classes would be active on different states.

The better way, I felt, would be to have a focus, hover, sm, md, etc attributes for each element so that it was visually easy to figure out what tailwind classes would activate on different states.

The Solution

Since HAML is a Ruby library, I solved the above with a bit of monkey patching. I added the following after my ApplicationHelper module code in app/helpers/application_helper.rb:

module Haml
  # Haml::AttriubuteParser parses Hash literal to { String (key name) => String (value literal) }.
  module AttributeParser
    class << self
      # @param  [String] exp - Old attributes literal or Hash literal generated from new attributes.
      # @return [Hash<String, String>,nil] - Return parsed attribute Hash whose values are Ruby literals, or return nil if argument is not a single Hash literal.
      def parse(exp)
        return nil unless hash_literal?(exp)

        hash = Hash.new{"\"\""}
        each_attribute(exp) do |key, value|
          if key.in?(['sm', 'md', 'lg', 'xl', 'focus', 'hover'])
            # Build up string to insert into class attribute
            tw_classes = value[1..-2]   # Remove the esacped quotes from the original string
                          .split        # Split into array
                          .map{ |clas| "#{key}:#{clas}" }
                          .join(" ")

            # Insert tw classes string before last escape quote
            hash['class'] = hash['class'].insert(-2, " #{tw_classes}")
          elsif key == 'class'
            hash['class'] = hash['class'].insert(-2, " #{value[1..-2]}")
          else
            hash[key] = value
          end
        end
        hash
      rescue UnexpectedTokenError, UnexpectedKeyError
        nil
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Now, in my views I'm able to style my elements with Tailwind CSS like so:

%input{class: "bg-gray-900 text-white pl-10 pr-4 py-2 rounded-lg w-full",
       sm: "bg-red-400 pl-2"
       focus: "outline-none bg-white text-gray-900",
       hover: "outline-none bg-white text-gray-900",
       placeholder: "Search by keywords"}
Enter fullscreen mode Exit fullscreen mode

The Solution Process

I figured this was a parsing problem so after a look at the HAML documentation, I figured I would have to monkey patch HAML::AttributeParser. Investigating the code further, there was only one method that made sense to patch: the parse method.

I copied and pasted the code over to my application_helper.rb and then inserted a raise to see if my code was affecting the parser and it was.

After a few puts, each followed by a raise, I found that attribute values were saved in the hash with double quotes escaped. Thus, an element with attributes of

{class: "bg-white text-black"}
Enter fullscreen mode Exit fullscreen mode

was saved in the local hash variable with a key of class and value of

"\"bg-white text-black\""
Enter fullscreen mode Exit fullscreen mode

Thus, all we have to do was handle the double quotes properly.

When inserting into the key of a hash, we have to insert before the last double quote using .insert(-2, <string>).

When handling the value string of an attribute, we have to remove the escaped double quotes using [1..-2].

I'm sure there's a more efficient way of achieving the above. If you have a better solution, please let me know by creating a PR / Discussion here.

Top comments (0)