When trying to develop a site with a consistent look and feel it’s common to develop a style guide with patterns which can be reused across the site. This lets designers easily reuse standard patterns and lets developers know how to make things look.
Historically, the rate at which we’ve developed GOV.UK has meant it’s been difficult to keep the style guide up to date with the rapidly changing look and feel of the site. We like to iterate a lot, and keeping a separate style guide up to date with these small and frequent changes has proven tricky.
This problem isn’t unique to GOV.UK and other people have tried to solve it. One of the issues that is more challenging in our system is that we use a micro-service architecture. This means we have many applications that render pages that need to look the same but actually share very little code.
The Lonely Planet engineers ran into a similar problem and developed their own gem for their applications to use. In their solution the gem contains the shared templates and assets, and runs as a webserver allowing it to render their living style guide.
We could foresee two main problems with using a gem to package our style guide. The first is keeping all of the projects pointing to the latest version of the gem. We have experienced this with our own frontend toolkit. Projects that aren't being actively developed often fall behind without us noticing. The second is that even when all the projects are pointing to the latest version, to get a change to the gem out you need to redeploy every application.
What we really wanted was the ability to update all the pages on GOV.UK without having to bump a gem in each repository and then re-deploy each application. So we spiked a couple of ideas and developed a minimum viable solution for how this could look.
There are four parts to our solution:
- defined pattern templates with documentation and assets
- a template resolver
- a internationalisation string loader
- an application to render the style guide
Let’s take a look at each of these in turn.
Component documentation, templates and assets
The most important thing for our components is the documentation. This defines the interface that applications can call the component with. It contains a set of fixtures to demonstrate how an application might call the component.
The templates themselves are
The component templates are then rendered as raw (un-rendered) files by a server which can be accessed by all of our other applications. On GOV.UK we use a gem called slimmer which adds our standard header and footer to every page. We’ve added in our component styles and assets to that header and footer template so they will be available on every frontend.
The next piece in the puzzle is our template resolver. We wrote a custom resolver which plugs into Rails to load templates over the network. When a partial is called which lives under the
govuk_component namespace the resolver will try and load that partial over the network.
So, in one of our rails applications, to get a component template you call:
<%= render partial: 'govuk_component/beta_notice' %>
That will call out to the network, get the erb template, store the template in a cache for up to 5 minutes and return the template contents for Rails to use as a standard
erb template as if it had been loaded from the local hard drive.
As lots of pages on GOV.UK can be translated in 50 different languages, we also need a way of providing translated strings for the components. We’ve achieved this in a similar way to the templates. We created a internationalisation backend which will fetch translation strings over the network from the same application as the component partials. They are then made available to the standard
I18n helpers. So using a string uses the standard Rails helper method and even supports the standard interpolation:
<%= t(‘govuk_component.metadata.from’) %>
This lets us manage the component templates and template strings in the same place so that when one is updated we can roll out the new translation in one commit and one deploy.
Rendered Style Guide
The last thing is a living live-updating style guide. To achieve this we’ve built a simple rails application that loads in the documentation that we looked at earlier. It has a page for each different component and uses the fixture data to show how different variations of the component can look. For example, here’s the guide for the metadata component.
What we’ve built is still minimum viable to get something working. There are many improvements we know we need to make. Things like:
- a warm component cache so each application can have the new templates pushed to them rather than each application having to fetch new templates independently
- a more sustainable documentation format for when a single YAML file is no longer sustainable
- the ability to have different kinds of fixtures. For example having: a set to show valid inputs; a set of extreme inputs for when we iterate the design; and another set to test possible input permutations for use in a test suite
What we’ve managed to build is a collection of things that extend the standard Rails way of working. So new developers don’t have to try and learn a new API and if we ever want to remove the component work or split a project away from it it would be easy to just copy the appropriate assets into that project.
At the moment we’ve avoided looking at supporting our components on anything other than Rails applications which run in our stack. This has mainly been down to time constraints. We’re aware there are many external services which would like to use our components. Thinking about a strategy for non-Rails to use components is a much bigger piece of work.
We’re still in the early phases of rolling out the shared components to our applications and I’m sure we’ll find many more edges to what we’ve built. But for the moment we’re excited about being able to update the look and feel of components in many applications powered by a single source.
If this sounds like a good place to work, take a look at Working for GDS - we're usually in search of talented people to come and join the team.