Darian's Blog

Rails Tailwind Components

Published:
Tags: Rails Tailwind

In this guide, we’re showing how we build components at FutureFund. We’re building a simple yet flexible avatar component in Rails using TailwindCSS, where user initials are displayed within a circular badge. We’re keeping it straightforward, implementing the component as a Rails helper without any additional libraries or complex abstractions. We’ve never found a need to go beyond Rails view helpers.

Setting Up the Basics

To start, create a dedicated subdirectory in your helpers folder for your design system. At FutureFund, we’re sticking to our bee theme and using Honey. You can name yours anything that suits your project. If you’re looking for a simple, functional name, Components works just fine.

The initial version of our helper is straightforward:

module Honey::AvatarHelper
  def avatar(initials)
    tag.div initials, class: "relative inline-flex items-center justify-center border rounded-full bg-slate-600 font-semibold text-white shrink-0 w-10 h-10 text-xs bg-slate-600 text-white"
  end
end

To use the component, simply call it in a view (assuming you use Slim templates):

= avatar user.initials

Adding Custom Classes

To give users control over styling, let’s enable additional classes to be passed in. This allows customization while keeping the base structure intact.

def avatar(initials, options = {})
  options.symbolize_keys!

  base_classes = %w[
    relative
    justify-center
    items-center
    inline-flex
    border
    rounded-full
    font-semibold
    shrink-0
    w-10
    h-10
    text-xs
    bg-slate-600
    text-white
  ]
  tag.div initials, class: [ base_classes, options[:class] ]
end

With a high number of classes, which is typical when using Tailwind, I find it easier to put one class per line as we see in base_classes.

With this setup, users can add custom styles:

= avatar user.initials, class: "bg-sky-600"

Adding Custom HTML Attributes

To add flexibility for additional HTML attributes, we’ll modify our method to include html_options:

def avatar(initials, options = {}, html_options = {})
  options.symbolize_keys!

  base_classes = %w[
    ...
  ]
  tag.div initials, class: [ base_classes, options[:class] ], **html_options
end

Variants

Variants add even more flexibility by allowing users to choose specific design options without manually setting classes. In our example, we introduce size and color variants:

def avatar(initials, options = {}, html_options = {})
  options.symbolize_keys!

  base_classes = %w[
    relative
    justify-center
    items-center
    inline-flex
    border
    rounded-full
    font-semibold
    shrink-0
  ]

  variants = {
    size: {
      sm: %w[ w-10 h-10 text-xs ],
      md: %w[ w-12 h-12 text-base ],
      lg: %w[ w-16 h-16 text-lg ]
    },
    color: {
      primary: %w[ bg-slate-600 text-white ],
      secondary: %w[ bg-sky-600 text-white ]
    },
    defaults: {
      size: :sm,
      color: :primary
    }
  }

  tag.div(
    initials,
    class: [ base_classes, extract_variants(variants, options), options[:class] ],
    **html_options
  )
end

And here’s the extract_variants helper:

def extract_variants(variants, options = {})
  extracted_values = []

  variants[:defaults].each_key do |variant_type|
    key = options[variant_type] || variants[:defaults][variant_type]
    extracted_values << variants.dig(variant_type, key.to_sym)
  end

  extracted_values
end

Now, you can use the avatar helper with variants:

= avatar user.initials, size: "sm", color: "primary"

Wrapping Up

This avatar component is a simple example how to build your design toolkit, providing options for custom styling and configuration that allow your team to match it seamlessly with the rest of your application’s UI. By using variants, you can maintain consistency while still providing enough flexibility for customization across different contexts.