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.
Allow admins to set brand color #10097
What type of PR is this? (check all applicable)
- [ ] Refactor
- [x] Feature
- [ ] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
Description
This feature allows admins to set their own main brand color....
This color needs to contrast properly with white, so I added the wcag contrast gem. I think this is a good gem to add, because we can start using this elsewhere, like user profile colors, tags, etc. Currently we let users set non-contrasting colors and basically just moderate it. This will help us ensure a minimum of 4.5:1 contrast for readability and accessibility for all.
The gem has not recently been updates, but it is a fairly simple low-bloat utility gem. The gem author is active and I feel like we can be comfortable that we could merge a change if we needed to.
Also added some basic validations in the controller. I figured we can start adding some validations in this area and extract these to a better place in a future refactor once we establish more of these.
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.
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....
adambutler / rails-settings-cached
This is improved from rails-settings, added caching for all settings
Settings Gem
This is improved from rails-settings added caching for all settings. Settings is a plugin that makes managing a table of global key, value pairs easy. Think of it like a global Hash stored in your database that uses simple ActiveRecord like methods for manipulation. Keep track of any global setting that you dont want to hard code into your rails app. You can store any kind of object. Strings, numbers, arrays, or any object.
Status
Setup
Edit your Gemfile:
gem 'rails-settings-cached', "~> 0.5.5"
Older Rails versions:
# 4.1.x
gem "rails-settings-cached", "~> 0.4.0"
# 4.0.x
gem "rails-settings-cached", "0.3.1"
# 3.x
gem "rails-settings-cached", "0.2.4"
Generate your settings:
$ rails g settings:install
If you want custom model name:
$ rails g settings:install MySetting
Now just put that migration in the database with:
rake db:migrate
Usage
The syntax is easy. First, lets create some settings to keep…
So in adding this feature, all we had to do is add a line to the config.
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...
mkdynamic / wcag_color_contrast
Calculate the WCAG contrast ratio between 2 colors.
WCAGColorContrast
A Ruby port of the Javascript https://github.com/doochik/wcag-color-contrast.
Calculates the contrast ratio between 2 colors, for checking against the WCAG recommended contrast ratio for legibility.
Installation
Add this line to your application's Gemfile:
gem 'wcag_color_contrast'
And then execute:
$ bundle
Or install it yourself as:
$ gem install wcag_color_contrast
Usage
With 2 hex colors as strings (3 or 6 characters, case insensitive, no leading pound/hash sign):
require 'wcag_color_contrast'
WCAGColorContrast.ratio('999', 'ffffff')
#=> 2.849027755287037
Can also calculate the relative luminance of a color
WCAGColorContrast.relative_luminance('008800')
#=> 0.17608318886144392
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request
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.
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...
<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!
Top comments (5)
This is very cool! Did you consider setting the default font colour for buttons etc to black when the chosen brand colour didn't meet the necessary contrast with white text? Just wondering as I've noticed in working with the React material-ui library this is what they do to adjust to the primary brand colour 🤔
Currently it only allows “dark” colors, but yeah I think a follow up will be to allow light colors and likewise change the color to black (or sufficiently dark complementary colors.)
Nice, and the way you guys have implemented it is totally open to that kind of extension later.
I love stuff like this that makes users' lives easier and promotes accessibility at the same time ♥️
Very cool Ben. I feel good that contrast thing sort of matches the one I threw together a couple of years ago 😅
Comment for #735
I've not ever written ruby before so feel free to write something yourself but something like this is what I was thinking
Then in
user_decorator.rb
you could say something likeYour dedication ❤