Design system options for Rails

Table of contents

You would think that picking a well-made design system for your application is a solved task in 2025. But not for Rails and therefore not for Business Class. When I started Business Class, there wasn't really a thing to adopt and somehow it still feels the same.

Building design systems

I am not a big designer, although I do try to improve in this regard (I bought the book Refactoring UI from the authors of Tailwind, among other things) and I have been part of implementing a design system a few times. The last time we were implementing it from scratch for Phrase with ViewComponent library.

I was originally thinking that Business Class should just adopt something that's already out there, because building things from scratch are super time-consuming. However, the choices weren't convincing at the time and I just started with Bulma. Later with the release of Tailwind 3 I made a switch and build a simple design with plain Tailwind and @apply .

I think the basic design works well, but it's not a component design system people might expect to work with in 2025. So let's reevaluate our options for the future and if we can finally pick something already made.

Component systems for Rails

There are still only a couple of options we can truly consider for Business Class. Please note that I can only include free or at least freemium design systems, not paid ones.

shadcn/ui

Shadcn/UI is an open-source collection of beautifully designed, reusable UI components built on top of React, Tailwind CSS, and Radix UI. It provides clean, accessible, and customizable building blocks that help developers quickly create modern web applications. Shadcn/UI emphasizes simplicity, developer experience, and adherence to industry-standard best practices, making it easy to integrate into various projects and workflows.

Since it's made for React you would use it like this:

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion"
 
export function AccordionDemo() {
  return (
    <Accordion type="single" collapsible className="w-full">
      <AccordionItem value="item-1">
        <AccordionTrigger>Is it accessible?</AccordionTrigger>
        <AccordionContent>
          Yes. It adheres to the WAI-ARIA design pattern.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger>Is it styled?</AccordionTrigger>
        <AccordionContent>
          Yes. It comes with default styles that matches the other
          components&apos; aesthetic.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-3">
        <AccordionTrigger>Is it animated?</AccordionTrigger>
        <AccordionContent>
          Yes. It's animated by default, but you can disable it if you prefer.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  )
}

If we want to use it in Rails, we have to provide our own implementation.

shadcn/ui on Rails

The original shadcn/ui is made for React and won't help us that much for a plain Hotwire-based apps. Luckily someone already started shadcn/ui on Rails, the shadcn/ui implementation with helpers, partials, and Stimulus controllers.

At first, this looks perfect. Getting a Rails version of a mature project is a win in itself, but the ability to build hybrid apps with React and use the same system is even better. However, that's a first look. The second look will reveal that it's not complete and 1:1 version of the original.

Here's how it looks like to use a component in a view:

<% items = [{:value => "Ruby on Rails", name: "Ruby on Rails"}, {:value => "Next.js", name: "Next.js"}, {:value => "Remix.run", name: "Remix.run"}] %>

<%= render_combobox items do %>
  <%= combobox_trigger do %>
    Select framework
  <% end %>
<% end %>

Still, I like the decision of using partials and Stimulus controllers and having everything wrapped in helpers. It's in and out a very Railsy approach. It's a strong contender.

daisyUI

Another component library for Tailwind CSS is daisyUI, designed to streamline web development by providing customizable, ready-to-use UI components. It's one of the most used alternatives to shadcn/ui with a decent history of its own.

daisyUI implements Tailwind class names for all common UI components so in practice we can use nice shortcuts like btn or card . It's little bit like Bootstrap and Bulma but on Tailwind. Personally, I appreciate the short class names and not polluting the HTML.

Here's an example:

<div class="avatar">
  <div class="w-24 rounded-xl">
    <img src="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" />
  </div>
</div>
<div class="avatar">
  <div class="w-24 rounded-full">
    <img src="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" />
  </div>
</div>

The latest daisyUI 5 that builds on Taiwind 4 looks really nice and offers a lot of nice prebuilt themes:

The biggest downside is that DaisyUI doesn't ship with JavaScript. It's primarily a visual system.

Flowbite

Flowbite is an open-source UI library built upon Tailwind CSS, offering a collection of various components and utilities to streamline the development of responsive and modern web applications.

What I like is that Flowbite officially support several framework including Rails. The JavaScript is generic and not React-specific. This alone is very nice since we don't need to reimplement anything ourselves.

Here's a snippet of a component in Flowbite:

<!-- Modal toggle -->
<div class="flex justify-center m-5">
    <button id="updateProductButton" data-modal-target="updateProductModal" data-modal-toggle="updateProductModal" class="block text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800" type="button">
    Update product
    </button>
</div>

<!-- Main modal -->
<div id="updateProductModal" tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-modal md:h-full">
    <div class="relative p-4 w-full max-w-2xl h-full md:h-auto">
        <!-- Modal content -->
        <div class="relative p-4 bg-white rounded-lg shadow dark:bg-gray-800 sm:p-5">
            <!-- Modal header -->
            <div class="flex justify-between items-center pb-4 mb-4 rounded-t border-b sm:mb-5 dark:border-gray-600">
                <h3 class="text-lg font-semibold text-gray-900 dark:text-white">
                    Update Product
                </h3>
                <button type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white" data-modal-toggle="updateProductModal">
                    <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
                    <span class="sr-only">Close modal</span>
                </button>
            </div>

As you can see you use the Tailwind directly together with data attributes and aria labels. Here's a corresponding JavaScript we need to add:

document.addEventListener("DOMContentLoaded", function(event) {
  document.getElementById('updateProductButton').click();
});

Unlike the previous options, Flowbite isn't entirely open source. Still, the basic building blocks are and could be enough for a new Business Class theme.

Preline

Preline is another freemium UI library built upon Tailwind CSS with its own framework-independent JavaScript. It features a lot of components and examples, including those for building marketing pages and what not.

Here's how an authentication box would look like in code with Preline:

<div class="mt-7 bg-white border border-gray-200 rounded-xl shadow-2xs dark:bg-neutral-900 dark:border-neutral-700">
  <div class="p-4 sm:p-7">
    <div class="text-center">
      <h1 class="block text-2xl font-bold text-gray-800 dark:text-white">Sign in</h1>
      <p class="mt-2 text-sm text-gray-600 dark:text-neutral-400">
        Don't have an account yet?
        <a class="text-blue-600 decoration-2 hover:underline focus:outline-hidden focus:underline font-medium dark:text-blue-500" href="../examples/html/signup.html">
          Sign up here
        </a>
      </p>
    </div>

    <div class="mt-5">
      <button type="button" class="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 shadow-2xs hover:bg-gray-50 focus:outline-hidden focus:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-900 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-800 dark:focus:bg-neutral-800">
        <svg class="w-4 h-auto" width="46" height="47" viewBox="0 0 46 47" fill="none">
          <path d="M46 24.0287C46 22.09 45.8533 20.68 45.5013 19.2112H23.4694V27.9356H36.4069C36.1429 30.1094 34.7347 33.37 31.5957 35.5731L31.5663 35.8669L38.5191 41.2719L38.9885 41.3306C43.4477 37.2181 46 31.1669 46 24.0287Z" fill="#4285F4"/>
          <path d="M23.4694 47C29.8061 47 35.1161 44.9144 39.0179 41.3012L31.625 35.5437C29.6301 36.9244 26.9898 37.8937 23.4987 37.8937C17.2793 37.8937 12.0281 33.7812 10.1505 28.1412L9.88649 28.1706L2.61097 33.7812L2.52296 34.0456C6.36608 41.7125 14.287 47 23.4694 47Z" fill="#34A853"/>
          <path d="M10.1212 28.1413C9.62245 26.6725 9.32908 25.1156 9.32908 23.5C9.32908 21.8844 9.62245 20.3275 10.0918 18.8588V18.5356L2.75765 12.8369L2.52296 12.9544C0.909439 16.1269 0 19.7106 0 23.5C0 27.2894 0.909439 30.8731 2.49362 34.0456L10.1212 28.1413Z" fill="#FBBC05"/>
          <path d="M23.4694 9.07688C27.8699 9.07688 30.8622 10.9863 32.5344 12.5725L39.1645 6.11C35.0867 2.32063 29.8061 0 23.4694 0C14.287 0 6.36607 5.2875 2.49362 12.9544L10.0918 18.8588C11.9987 13.1894 17.25 9.07688 23.4694 9.07688Z" fill="#EB4335"/>
        </svg>
        Sign in with Google
      </button>

      <div class="py-3 flex items-center text-xs text-gray-400 uppercase before:flex-1 before:border-t before:border-gray-200 before:me-6 after:flex-1 after:border-t after:border-gray-200 after:ms-6 dark:text-neutral-500 dark:before:border-neutral-600 dark:after:border-neutral-600">Or</div>

RubyUI

So far, we looked at popular design systems and their use in Rails. But there is one significant effort being developed directly for Ruby called RubyUI. It's also built on top of Tailwind and provides easy installation instructions for both JavaScript bundlers and Importmaps.

The whole project is really full of green flags, but it's not without a potential downside. It's a system made for Phlex, an alternative view layer for Ruby. If you have never heard about it, here's how the component might look like:

AspectRatio(aspect_ratio: "4/3", class: "rounded-md overflow-hidden border shadow-sm") do
  img(
    alt: "Placeholder",
    loading: "lazy",
    src: image_path('pattern.jpg')
  )
end

It's a pure Ruby component that will be included in pure Ruby view. So no ERB or anything like that. On one hand it's pretty cool, on the other I am not 100% convinced to leave ERB for greener pastures. I always loved being able to just work inside HTML.

Conclusion

So what have we learned? The choices are a bit better than before, but there is not a single ultimate choice outshining the others. One thing is clear tho, going the Tailwind route was likely a good choice, and we should keep building on top of Tailwind whether that's with a pre-made UI or Business Class own theme.


© Business Class Blog