Skip to main content
Warning This document has not been updated for a while now. It may be out of date.
Last updated: 1 Nov 2023

govuk_publishing_components: Set up individual component CSS loading

Set the component gem up so that rendering applications can (optionally) load each component's stylesheet individually, as described in RFC #149.

TL;DR

An array for stylesheet names is set up in the rendering application using a helper. Templates can then add the names of stylesheets to the array, which is then used by the application to output all the stylesheets needed on the page.

Example of rendering application upgrade: https://github.com/alphagov/government-frontend/pull/2734

Initial set up

There are 3 types of stylesheet that are used across GOVUK:

  • CSS from components in the gem
  • CSS from components in the application
  • CSS from views in the application

The helper adds each type of stylesheet to the array - gem components, app components, and views.

Any other styles should be added in the application's application.scss file.

Four shorthand methods for adding each type of stylesheet to the array:

  • add_gem_component_stylesheet
  • add_app_component_stylesheet
  • add_view_stylesheet
  • add_stylesheet_path

The first three take the name of the Sass file relevant to the component or view:

add_gem_component_stylesheet("accordion")
add_app_component_stylesheet("calendar")
add_view_stylesheet("homepage")

There is also 1 generic method that takes a file path:

add_stylesheet_path("views/dir-1/_partial.css")

Precompiling

Gem component stylesheets are automatically precompiled. Application component and view stylesheets should be added to the application's manifest file to be precompiled, included and served.

Known problems

Nested components in GOV.UK Frontend

GOV.UK Frontend has components that use other components - for example, both the radio button and character count component include the label component. These are deduped by GOV.UK Frontend when compiling the CSS to a single file.

As this pull request includes the stylesheets for the components individually, this will mean that both the radio stylesheet and the character count stylesheet will include the label component.

This shouldn't make a difference in visual appearance of the site, but does mean some CSS is unnecessarily included twice.

The page sizes were calculated from individual compiled CSS files, so there still is a performance improvement - but it could be further improved.

Ideally there'd be a way of including only the components without including the nested components; for example, the partial for radio inputs would include both the label and radio:

add_gem_component_stylesheet("label")
add_gem_component_stylesheet("radio")

which would then be deduped before being added to the array of stylesheets.

Increased request header size

Starting with Rails 6.1 preload_links_header is set to true by default, this ensures the javascript_include_tag and stylesheet_link_tag will generate a Link header that preloads assets.

If your application is using the individual loading of stylesheets feature and preload_links_header is set to true, you will likely see an increase in the header size for each page. This is important to be aware of as you may need to configure your web server to allow for larger header sizes, example PR - increasing the cache size in NGINX.

Step by step changes to the rendering application

Add stylesheets to be precompiled

Add stylesheet paths in app/assets/config/govuk_publishing_components_sassc-rails_manifest.js:

+  //= link govuk_publishing_components/components/_accordion.css
+  //= link govuk_publishing_components/components/_action-link.css
+  //= link govuk_publishing_components/components/_attachment.css
+  //= link govuk_publishing_components/components/_attachment-link.css
   ...

Note, that while GOV.UK Publishing Components supports applications which include both LibSass or Dart Sass, you'll need to add and remove stylesheet paths in the Sprockets manifest as detailed above.

Remove gem component imports from app/assets/stylesheets/application.scss:

- @import "govuk_publishing_components/components/accordion";
- @import "govuk_publishing_components/components/action-link";
- @import "govuk_publishing_components/components/attachment";
- @import "govuk_publishing_components/components/attachment-link";
  ...

Using the publishing components gem without the static rendering app

The static rendering application already includes several components from the publishing components gem - static application.scss

To avoid requesting the same stylesheet twice, we exclude any stylesheets included in static by default.

If you are using the individual assets loading feature in an app that does not rely on static, we recommend adding the config file below in your application to ensure that the components render correctly.

# config/initializers/govuk_publishing_components.rb
GovukPublishingComponents.configure do |c|
  c.exclude_css_from_static = false
end

Add the app components and views to the stylesheet array

This bit will likely take the most time - and really benefits from a comprehensive list of what pages use which templates (and template variations).

Add the add_view_stylesheet and add_app_component_stylesheet helpers to each view, usually this will be at the top of the file.

add_view_stylesheet("homepage")
add_app_component_stylesheet("calendar")

Each stylesheet that is used individually will need an extra @import added to allow them to use GOV.UK Frontend variables and mixins:

@import "govuk_publishing_components/individual_component_support";

Hoist body content to the top of your layouts

This will ensure that all components and views are discovered, before render_component_stylesheets is called, and their respective stylesheets added inside the <head>. Otherwise, components and view partials added in body content are not rendered in time and associated stylesheets are not included.

<% content_for :body do %>
  <main id="content">
    <%= yield %>
  </main>
<% end %>

<!DOCTYPE html>
...

Use yield to add body content between <body> and </body>:

<%= yield :body %>
Add the stylesheets just before the closing </head> tag of the base template
  <head>
    <meta charset="utf-8" />
    <%= stylesheet_link_tag 'application', :media => "all", integrity: false %>
    ...
    <%=
      render_component_stylesheets
    %>
  </head>

Check stylesheets are loading individually

When the setup is complete, only the component stylesheets required to render the page are requested. For example, the screenshot below displays network activity for a browse page e.g. benefits and shows separate network requests for the browse, lead-paragraph and cards stylesheets:

Network requests for individual stylesheets

Why

See RFC #149 for more details about this approach.