CSS Variables and Redis: How color customization is implemented on Forem
Tags: forem, css, rails, a11y
Published: 2020-09-04 15:13:27

We just shipped this pull request into Forem which allows forem admins to define the core brand color of a new Forem, and I thought it was worth sharing and talking about the approach.

{% github https://github.com/forem/forem/pull/10097 %}

If you're not aware, Forem is the open source platform that powers DEV, a few other communities, and we will be launching much more functionality to support a broad array of communities in the coming weeks and months, getting closer to the infrastructure generalization which will make this possible en masse. Along the way we continue to make improvements to the tools available to help each forem succeed on their own terms, and exist within a diverse, but cohesive ecosystem.

{% link https://dev.to/devteam/for-empowering-community-2k6h %}

## Back to colors...

In building out our customization of the software, we are trying to be gradual in our approach. It's much easier to add a customization feature than to take it away later, and we don't want to get to the point where Forems are over-stuffed with features and have degrading performance.

So with that in mind, the most recent functionality that shipped is one which allows Forem admins to designate their primary brand color— which is used across the platform on buttons, and call-to-actions.  

## Server side implementation

We use a cached settings Ruby gem to implement the whole suite of site-wide options that admins can vary....

{% github https://github.com/adambutler/rails-settings-cached %}

So in adding this feature, all we had to do is add a line to the config.

```ruby
field :primary_brand_color_hex, type: :string, default: "#3b49df"
```

But there are more concerns than this on the server side, we need to validate that value matched the proper hex pattern of `/\A#(\h{6}|\h{3})\z/`. This was pretty straightforward and can be seen in the pull request. The more interesting part was validating for an accessible color contrast.

In the current functionality, the color needs to be dark enough to contrast against white (e.g. the button text color), so we imported a gem for that...

{% github https://github.com/mkdynamic/wcag_color_contrast %}

In order to simplify our interface and ensure that we don't need to change too much in the code should this dependency become unreliable in the future, we also established a `Color::Accessibility` object which can be used wherever we need it.

```ruby
module Color
  class Accessibility
    def initialize(hex)
      @hex = hex.delete("#")
    end

    def low_contrast?(compared_color = "ffffff", min_contrast = 4.5)
      WCAGColorContrast.ratio(@hex, compared_color.delete("#")) < min_contrast
    end
  end
end
```

Further uses of this could be validations on user-defined profile colors. Currently we have _some_ functionality to try and ensure contrast, but it is a poor implementation and we cannot guarantee the WCAG standard of 4.5:1 contrast ratio on profile colors.

## Front end implementation

The CSS required to make this work is quite minimal... because of the great work that had gone into our CSS system prior to this functionality. We have already established a set of CSS variables used throughout the site, so that we only have to change a few lines in the application and everything else should fall in line.

This snippet was added directly into the `html.erb` file which is inserted into the `head` of the document...

```html
<style>
  :root {
    --accent-brand: <%= SiteConfig.primary_brand_color_hex %>;
    --accent-brand-darker: <%= HexComparer.new([SiteConfig.primary_brand_color_hex]).brightness(0.85) %>;
    --accent-brand-lighter: <%= HexComparer.new([SiteConfig.primary_brand_color_hex]).brightness(1.1) %>;
    --accent-brand-a10: #{rgba(<%= SiteConfig.primary_brand_color_hex %>, 0.1)};
  }
</style>
```

And that is all it takes for the rest of the CSS to respect the `--accent-brand` variables throughout. Check out the above pull request to take a peak at all the code and conversation that went into this change.

Happy coding!