Skip to content

Latest commit

 

History

History
417 lines (286 loc) · 16.5 KB

component_conventions.md

File metadata and controls

417 lines (286 loc) · 16.5 KB

Component Conventions

Namespacing

All components must use a namespace with a c as part of the namespace -- the c is for component. For example .app-c-banner.

Do not use the .govuk- namespace.

The namespace indicates where a component lives. A single page on GOV.UK could render components from multiple places.

Prefix Place
.app-c- Component lives within the frontend application
.gem-c- Component originates from the govuk_publishing_components gem
.govuk- Component originates from GOV.UK Frontend

Structure

Type Location Example Description
Template app/views/components _my_comp.html.erb Template logic and markup
Documentation app/views/components/docs my_comp.yml Describes the component
Styles app/assets/stylesheets/components _my-comp.scss Component styles
Print styles Include in screen styles Print styles
Images app/assets/images/govuk_publishing_components my-comp.png Images
Scripts app/assets/javascripts/components my-comp.js JavaScript enhancements
Tests spec/components my_comp_spec.rb Unit tests
JavaScript tests spec/javascripts/components my-comp-spec.js Unit tests
Helpers lib/govuk_publishing_components/presenters my_comp_helper.rb Helpers

Template

The template logic and markup. The template defines the component’s API. Filename must begin with an underscore.

If complex logic is required this should be handled using a helper.

Example:

<%
  options ||= []
  id ||= false
  helper = GovukPublishingComponents::Presenters::MyComponentHelper.new(options)
%>

<% if options.any? %>
  <div class="govuk-something" id="<%= id %>">
    <h2>An example component</h2>
    <%= helper.someContent %>
  </div>
<% endif %>

If a component includes a heading, consider including an option to control the heading level (see the heading component for example).

Components can use other components within their template, if required (see the input component for example).

Complex components can be split into smaller partials to make them easier to understand and maintain (see the feedback component for example).

Components should not have an option to include arbitrary classes as this could violate the principle of isolation. If there is a requirement for a styling variation on a component this should be included as an option e.g. small: true.

Write documentation

Each component is represented with a single .yml file. eg lead_paragraph.yml

The .yml file must have:

Property Required Description
filename Required Filename of .yml file must match component partial name
name Required Friendly name for component
description Required One or two sentences summarising the component
body Optional A govspeak markdown block giving guidance for the component
accessibility_criteria Required A govspeak markdown block listing the accessibility acceptance criteria this component must meet to be accessible
examples Required Each block represents an example and each example is listed in the component guide. Examples must cover all expected use cases.

Example YAML file

name: Name of component
description: Short description of component
body: |
  Optional markdown providing further detail about the component
accessibility_criteria: |
  Markdown listing what this component must do to be accessible. For example:

  The banner must:

  - be visually distinct from other content on the page
  - have an accessible name that describes the banner as a notice
shared_accessibility_criteria:
  - link
examples:
  default:
    data:
      some_parameter: 'The parameter value'
    description: |
      This component is used in the following contexts:

      - [the GOV.UK homepage](https://www.gov.uk)
  another_example:
    data:
      some_parameter: 'A different parameter value'

YAML configuration for a component which accepts a block

Some components can accept a block as an argument:

<%= render "my-accepts-block-component", { param: value }, do %>
  <span>Some text</span>
<% end %>

To configure the block in the component YAML file you should specify a block key in the example data:

examples:
  default:
    data:
      some_parameter: 'The parameter value'
      block: |
        <span>Some text</span>

YAML configuration for components that need contextual HTML

If a component is only visible, or behaves differently, in a certain context the examples for it can be embedded within HTML using the embed option:

<button class="trigger-for-component">Click me</button>
<%= render "my-hidden-by-default-component", { param: value } %>

To configure a HTML embed in the component YAML file you can specify embed at the root or individual examples:

embed: |
  <button class="trigger-for-component">Click me<button>
  <%= component %>
examples:
  default:
  different_embed_example:
    embed: |
      <button class="different-trigger-for-component">Click me<button>
      <%= component %>

Markdown listing what this component must do to be accessible.

Shared accessibility criteria can be included in a list as shown. They are pre-written accessibility criteria that can apply to more than one component, created to avoid duplication. For example, links within components should all accept focus, be focusable with a keyboard, etc.

A component can have accessibility criteria, shared accessibility criteria, or both.

Description

An example can have an optional description. This is a govspeak markdown block.

Providing context to examples

A context block can be passed to examples. The guide currently supports right_to_left and dark_background contexts. For example:

examples:
  right_to_left_example:
    data:
      some_parameter: 'عربى'
    context:
      right_to_left: true

The component guide will wrap the example with a direction-rtl class. It is expected that the host application will set the text direction using the class in a parent element using the following CSS:

.direction-rtl {
  direction: rtl;
  text-align: start;
}

The component guide will wrap a dark_background context example with a dark-background class that sets the parent element background color to govuk-blue. The component should either already work on a dark background or contain a param that, when set to true, allows it to work against darker colours.

Styles

With the exception of namespaces, follow the GOV.UK Frontend CSS conventions, which describes in more detail our approach to namespaces, linting and BEM (block, element, modifier) CSS naming methodology.

Components can rely on classes from GOV.UK Frontend to allow for modification that build on top of the styles from the Design System. This follows the recommendations for extending from the Design System guide.

For example, extending the button from GOV.UK Frontend could be done like so:

<button class="govuk-button gem-c-button--inverse">
  Inverse button
</button>

This makes it clear what the base component is, what the modifier is, and where the modifications are coming from.

BEM

.block {}

.block__element {}

.block--modifier {}

.block__element--modifier {}

All CSS selectors should follow the BEM naming convention shown above, explained in more detail here.

Note: to avoid long and complicated class names, we follow the BEM guidance that classes do not have to reflect the nested nature of the DOM. We also try to avoid nesting classes too deeply so that styles can be overridden if needed.

  // Avoid this:
  .block__elem1__elem2__elem3

  // Instead use:
  .block__elem1
  .block__elem2
  .block__elem3

Using BEM means we have confidence in our styles only being applied within the component context, and never interfering with other global styles. It also makes it clearer how HTML elements relate to each other.

Visit the links below for more information:

Layout

New components should be built with a bottom margin and no top margin.

A standard for options to control this spacing has not been decided upon yet, although it is likely we will adopt something using the Design System spacing.

Print styles

Print styles should be included in the main stylesheet for a component, using the print media query as shown below.

.gem-c-example {
  background: red;

  @include govuk-media-query($media-type: print) {
    background: none;
  }
}

Linting

All stylesheets must be linted according to the style rules in govuk-lint.

# Lint Sass in your application components using govuk-lint
bundle exec govuk-lint-sass app/assets/stylesheets/components

Images

Images must be listed in config/initializers/assets.rb and can be referred to in Sass as follows.

background-image: image-url("govuk_publishing_components/search-button.png");

SVGs can also be used for images, ideally inline in templates and compressed.

JavaScript

Follow the GOV.UK Frontend JS conventions.

Scripts should use the module pattern and be linted using StandardJS.

Most components should have an option to include arbitrary data attributes (see the checkboxes component for example). These can be used for many purposes including tracking (see the select component for example code) but specific tracking should only be added to a component where there is a real need for it.

Some common JavaScript modules are available. If new functionality is required, consider adding it as a common module.

Tests

Component tests should include a check that the component doesn't fail if no data is passed.

JavaScript tests should be included if the component has any JavaScript that is unique to it. Use of existing JavaScript modules should be covered by existing tests.

Read further guidance on testing components.

Helpers

Source

Any code needed by a component that is more complex than basic parameter initialisation should be included in a separate file rather than in the template. There are 2 types of helper classes in this app:

  • AppHelpers. Are exposed to the applications using this gem. They should be documented using RDoc.

  • Component Presenters. Anything in these classes is only for use within the components. New presenter files must be included in lib/govuk_publishing_components.rb to work. They should be marked @private.

Code can be called and referred to in the template as follows:

<%
  card_helper = GovukPublishingComponents::Presenters::ImageCardHelper.new(local_assigns)
%>

<%= card_helper.heading_tag %>

Shared helper

There is a shared helper that can provide common functionality to all components. This includes:

  • set margin bottom and top
  • set heading level and heading font size
  • allow JavaScript classes to be added that begin with js- but reject any others
  • translation helpers

The following is an example of how to use the shared helper to set margin bottom on a component.

Add the shared helper to the component template:

shared_helper = GovukPublishingComponents::Presenters::SharedHelper.new(local_assigns)

Call the shared helper to provide a margin bottom class:

classes << shared_helper.get_margin_bottom

The component will accept a number for margin_bottom from 0 to 9 (0px to 60px) using the GOV.UK Frontend spacing scale. It defaults to a margin bottom of 15px (3). If you require a different default value, change the code as shown.

local_assigns[:margin_bottom] ||= 0 # this will be the default
shared_helper = GovukPublishingComponents::Presenters::SharedHelper.new(local_assigns)

Tests should be added to the component to check that the default margin bottom is correct and that the expected given margin bottom is set. The shared helper has its own tests to check that it returns the correct values.

See components that use the shared helper for further examples of usage, including the accordion, attachment, button, heading, radio, and others.

Passing HTML to a component

Avoid marking HTML as safe within components, this means avoiding use of raw or html_safe within a component's view.

By doing this we can avoid the risk of a Cross Site Scripting (XSS) attack.

Only sanitise HTML when the component is used in the application.

There are a few methods to achieve this:

Yielding blocks with single slots for nested children

Similar to HTML, there may be a clear slot where nested children should appear in a component.

For a panel component, you may expect anything nested within it to appear at the bottom of the component.

Do not:

<%= render 'panel', content: "Your reference number<br><strong>HDJ2123F</strong>" %>

Do:

<%= render 'panel' do %>
  Your reference number
  <br>
  <strong>HDJ2123F</strong>
<% end %>

Parameters with HTML for multiple slots

If you have multiple slots where HTML may go, you could consider passing them as parameters.

Note: If you can avoid a requirement for HTML this may be better. In the following example you may consider title: { level: 1, text: 'Application complete' }.

Do not:

<%= render 'panel', { title: '<h1>Application complete</h1>' } do %>
  Your reference number
  <br>
  <strong>HDJ2123F</strong>
<% end %>

Do:

<% panel_title = capture do %>
  <h1>Application complete</h1>
<% end %>
<%= render 'panel', { title: panel_title } do %>
  Your reference number
  <br>
  <strong>HDJ2123F</strong>
<% end %>

or (if the data is passed from a presenter / controller)

<%= render 'panel', { title: presented_panel_title.html_safe } do %>
  Your reference number
  <br>
  <strong>HDJ2123F</strong>
<% end %>