Building components and style guides in Rails

Recently I’ve been working on a Rails project where we built a component library and a style guide using Ruby and ERB. Before looking at how we did that, let’s talk about the whats and the whys.

The concept of modularity has been a part of computer science and programming for a long time. Splitting things into smaller, independent, and more manageable chunks is considered good practice for many reasons. When you apply separation of concerns and the single responsibility principle, you open up for code reuse, and you end up with a system that is easier to comprehend, update and test.

So what about design? Modularity in web design is comparatively new, and has evolved as a strategy to deal with an ever growing complexity in terms of browsers, resolutions and devices. Gone are the days when you could design for a handful of browsers for the desktop. Now, designers have to deal with phones, tablets, screen readers, TV screens, watches, you name it. As it turns out, splitting things into smaller, independent, and more manageable chunks is viable also when designing interfaces.

Designers and developers are already strapped for time and resources, and they’re now being tasked with making interfaces that look and function beautifully in any environment. That’s a very tall order.

Brad Frost

These independent chunks of interface are usually referred to as user interface components, or patterns. Think buttons, navbars, cards, breadcrumbs etc. A collection of such components is referred to as a component/pattern library. The component library is a part of a larger design system, which comprises many other things, such as brand identity, writing, tone & voice, colors, typography, code conventions etc. Sometimes a design system is represented in the form of a style guide.

Component libraries make it easier to create a consistent interface, for end users and developers both, and speeds up development. Using an out-of-the-box library, such as Bootstrap, allows designers to focus on the composition of components instead of spending tons of time making the individual pieces work in every possible environment, and for every possible audience. Think of it as limiting the surface area of the design API. However, we don’t want every website and app interface to look (or work) the same, so companies and organizations build their own. Check out IMB’s Carbon Design System, Google’s Material Design, The U.S. Web Design System and Atlassian Design for examples.

Because component libraries are a form of documentation, it is important to keep them up to date, in order for them to be useful. Many component libraries in the past were static, in that they were not tied to actual production code. Take Bootstrap for instance. You’re supposed to copy/paste markup directly from the documentation. If the internals of a component changes, the markup must be updated throughout the code base. Connecting the component library to production code is trickier, and is referred to as the Holy Grail by Brad Frost. Be sure to check out his book Atomic Design for more on the benefits of design systems and component libraries.

Building components in Rails

The building blocks of a component are the HTML, CSS and logic that makes up a single, reusable piece of interface. Components can also be combined to create bigger and more complex components, in the same way we can build complex computer systems out of small, isolated code modules. While the CSS aspects are interesting, we’re going to focus on the HTML and the logic for the rest of this blog post.

As an aside, CSS itself can also be modular. This is a huge topic, but read up on Tachyons, Tailwind, Atomic CSS and other functional CSS systems if you’re interested. While I personally subscribe to the usage of small utility classes when writing CSS, I also believe many of the problems functional CSS is trying to solve can be mitigated (to a degree) by having a design system and a component library, thereby forcing designers to think before adding more variations and New Stuff™. In other words, the better your design is structured, the better your chances are of writing and maintaining good CSS.

React is a popular framework for building web frontends, and in many ways it lends itself perfectly to building user interface components. The entire framework is built around the concept of components (even if it’s important to remember that a React component is NOT the same thing as the user interface components we have so far been talking about) and there are even tools out there to generate component libraries from them, such as Storybook and React Styleguideist. This makes React a good choice if you’re chasing the Holy Grail.

At Varvet we’ve used React together with Rails in a few projects and I have enjoyed it. Sometimes though, we build projects where it just doesn’t make sense to go down the SPA road. Recently I’ve been working on a more traditional project where we render (almost) everything server-side using ERB, and where there was a desire from the client to create a component library and a style guide. Rails partials and helpers leave a lot to be desired, and while there are a few alternatives out there for writing components in Rails, such as Cells, I was interested in trying to implement my own, and to solve a pet peeve of mine coming from React. The result of my experiments is available at https://github.com/jensljungblad/components. Let’s dive in.

Components live in app/components and can be created with a generator:

$ bin/rails g components:component alert

This will create the following files:

app/
  components/
    alert/
      _alert.html.erb
      alert.css
      alert.js
    alert_component.rb

Let’s add some markup:

<% # app/components/alert/_alert.html.erb %>

<div class="alert alert--primary" role="alert">
  Message
</div>

This component can now be rendered using the component helper:

<%= component "alert" %>

Attributes and blocks

There are two ways of passing data to components: attributes and blocks. Attributes are useful for data such as ids, modifiers and data structures (models etc). Blocks are useful when you need to inject HTML content into components.

Let’s define some attributes for the component we just created:

# app/components/alert_component.rb %>

class AlertComponent < Components::Component
  attribute :context
  attribute :message
end
<% # app/components/alert/_alert.html.erb %>

<div class="alert alert--<%= alert.context %>" role="alert">
  <%= alert.message %>
</div>
<%= component "alert", message: "Something went right!", context: "success" %>
<%= component "alert", message: "Something went wrong!", context: "danger" %>

To inject some HTML content into our component we can print the component variable in our template, and populate it by passing a block to the component helper:

<% # app/components/alert/_alert.html.erb %>

<div class="alert alert--<%= alert.context %>" role="alert">
  <%= alert %>
</div>
<%= component "alert", context: "success" do %>
  <em>Something</em> went right!
<% end %>

Elements

Attributes and blocks are great for simple components or components backed by a data structure, such as a model. Other components are more generic in nature and can be used in a variety of contexts. Often they consist of multiple parts or elements, that sometimes repeat, and sometimes need their own modifiers.

Take a card component. In React, a common approach is to create subcomponents:

<Card flush={true}>
  <CardHeader centered={true}>
    Header
  </CardHeader>
  <CardSection size="large">
    Section 1
  </CardSection>
  <CardSection size="small">
    Section 2
  </CardSection>
  <CardFooter>
    Footer
  </CardFooter>
</Card>

This right here is the pet peeve that I mentioned before. There are two problems with this approach:

  1. The card header, section and footer have no standalone meaning, yet we treat them as standalone components. This means a CardHeader could be placed outside of a Card.
  2. We lose control of the structure of the elements. A CardHeader can be placed below, or inside a CardFooter.

Using this gem, the same component can be written like this:

# app/components/card_component.rb %>

class CardComponent < Components::Component
  attribute :flush

  element :header do
    attribute :centered
  end

  element :section, multiple: true do
    attribute :size
  end

  element :footer
end
<% # app/components/card/_card.html.erb %>

<div class="card <%= "card--flush" if card.flush %>">
  <div class="card__header <%= "card__header--centered" if card.header.centered %>">
    <%= card.header %>
  </div>
  <% card.sections.each do |section| %>
    <div class="card__section <%= "card__section--#{section.size}" %>">
      <%= section %>
    </div>
  <% end %>
  <div class="card__footer">
    <%= card.footer %>
  </div>
</div>

Elements can be thought of as isolated subcomponents, and they are defined on the component. Passing multiple: true makes it a repeating element, and passing a block lets us declare attributes on our elements, in the same way we declare attributes on components.

In order to populate them with data, we pass a block to the component helper, which yields the component, which lets us set attributes and blocks on the element in the same way we do for components:

<%= component "card", flush: true do |c| %>
  <% c.header centered: true do %>
    Header
  <% end %>
  <% c.section size: "large" do %>
    Section 1
  <% end %>
  <% c.section size: "large" do %>
    Section 2
  <% end %>
  <% c.footer do %>
    Footer
  <% end %>
<% end %>

Multiple calls to a repeating element, such as section in the example above, will append each section to an array.

Helper methods

In addition to declaring attributes and elements, it is also possible to declare helper methods. This is useful if you prefer to keep logic out of your templates. Let’s extract the modifier logic from the card component template:

# app/components/card_component.rb %>

class CardComponent < Components::Component
  ...

  def css_classes
    css_classes = ["card"]
    css_classes << "card--flush" if flush
    css_classes.join(" ")
  end
end
<% # app/components/card/_card.html.erb %>

<%= content_tag class: card.css_classes do %>
  ...
<% end %>

Building style guides in Rails

We’ve paired the component library with a style guide library, available at https://github.com/jensljungblad/styleguide. The idea was to bundle the two in the same library, but they remain separate for now as the style guide is much more experimental. It works by first running an install generator:

$ bin/rails g components:install

This will create the following files and directories:

app/
  layouts/
    styleguide/
      example.html.erb
  views/
    styleguide/
      01_home.md

The style guide can be mounted in your routes file with:

mount Styleguide::Engine => "/styleguide"

You can create style guide pages simply by adding markdown files to the app/views/styleguide directory. These can be structured by putting them in subdirectories, and sorted by prefixing the file names with a digit.

This allows you to create any type of style guide, not limited to a component library. Check out Brad Frost’s Style Guide Guide for style guide inspiration.

Component library

To build the component library part of the style guide, and to bring us closer to the Holy Grail, a special markdown syntax, inspired by Catalog, can be used to render examples of any erb code on the style guide page, in the context of your own application:

# Alert

```example
<%= component "alert", message: "Something went right!", context: "success" %>
```

```example
<%= component "alert", message: "Something went wrong!", context: "danger" %>
```

This will render the component inside an iframe running in the context of your application, as well as the ERB code, syntax highlighted.

Conclusion

The component library has been working out well so far. It took a few iterations to arrive at the API, especially figuring out the concept of elements. What has been very useful is the ability to extract modifier and other logic to the component classes. One thing we’ve noticed is that we almost always want the ability to pass in classes and data attributes. Classes because we want to add utility classes such as margins to the component, and data attributes because we want to add Stimulus targets. Perhaps there should be out-of-the-box support for these types of attributes.

When it comes to the style guide and component library it’s not been in use for as long, and is still a bit rough around the edges. One interesting question we’ve been asking ourselves is: what components belong in a component library? We started with the assumption that mostly the generic, “building block” components, such as Alert, Card and Modal would go there. The types of components you inject content into. But we’ve been adding more and more app specific components as well, such as CourseCard and LessonCard, that are backed by a model. Even when they are only used in a single location, the ability to see all the possible state variations in the style guide is pretty neat. We don’t know yet if it’s a good way to go, but we’ll keep experimenting.

This blog post owes a lot to Brad Frost and his book Atomic Design. If you haven’t already, make sure to check it out.