hmrc / play-frontend-hmrc

Apache License 2.0
13 stars 11 forks source link

play-frontend-hmrc

This library contains all the Twirl components and helpers needed to implement frontend microservices on the HMRC tax platform.

play-frontend-hmrc is a Scala Twirl implementation of govuk-frontend and hmrc-frontend, adding to it Play, HMRC and tax platform-specific components and helpers that make the process of implementing frontend microservices straightforward and idiomatic for Scala developers.

Table of Contents

Getting started

Compatible Scala and Play Framework versions

This library is currently compatible with:

Understanding library changes between versions

We summarise what's changed between versions, and importantly any actions that may be required when upgrading past a specific version within our changelog.

Integrating with play-frontend-hmrc

  1. Add the library to the project dependencies:

    libraryDependencies += "uk.gov.hmrc" %% "play-frontend-hmrc-play-xx" % "x.y.z"

    Where play-xx is your version of Play (e.g. play-30).

    Note, this has changed since 7.x.x, previously the play version was included in the artefact version, it is now included in the artefact name.

  2. Add a route for the hmrc-frontend static assets in conf/app.routes:

    ->         /hmrc-frontend                      hmrcfrontend.Routes
  3. Define your service name in your messages files. For example,

    service.name = Any tax service

    If you have a dynamic service name you can skip this step and pass the serviceName into hmrcStandardPage or hmrcStandardHeader.

  4. Create a layout template for your pages using the HMRC standard page template

  5. Problems with styling? Check our Troubleshooting section.

Finding Twirl templates for GOV.UK and HMRC design system components

We provide example templates using the Twirl components through a Chrome extension. Please refer to the extension’s github repository for installation instructions.

With the extension installed, you can go to the GOVUK Design System components or HMRC Design System patterns pages, click on a component on the sidebar and see the Twirl examples matching the provided Nunjucks templates.

Using the components

To use our components and helpers, you will first need to import them from their corresponding packages in uk.gov.hmrc.govukfrontend.views.html.components, uk.gov.hmrc.hmrcfrontend.views.html.components or uk.gov.hmrc.hmrcfrontend.views.html.helpers.

For most components, their parameters are encapsulated within view models, case classes that live within a subpackage of uk.gov.hmrc(govuk|hmrc)frontend.views.viewmodels and are aliased for use under uk.gov.hmrc(govuk|hmrc)frontend.views.html.components.

The following will import all components, helpers, view models, and implicits. This is the most succinct import method, but may require additional imports to resolve ambiguous import compilation errors for some view models. It may also cause unused import warnings.

@import uk.gov.hmrc.govukfrontend.views.html.components._
@import uk.gov.hmrc.govukfrontend.views.html.components.implicits._

Alternatively, you can import components and view models individually to avoid the possibility of ambiguous import compilation errors and unused import warnings.

@import uk.gov.hmrc.govukfrontend.views.html.components.GovukRadios                                  /* component */
@import uk.gov.hmrc.govukfrontend.views.html.components.{Radios, Fieldset, Legend, Text, RadioItem}  /* viewmodel case classes */
@import uk.gov.hmrc.govukfrontend.views.html.components.implicits._

You can then use the components in your templates as follows:

@this(govukRadios: GovukRadios)

@(myForm: Form[_])

@govukRadios(Radios(
 fieldset = Some(Fieldset(
   legend = Some(Legend(
     content = Text("Where do you live?"),
     classes = "govuk-fieldset__legend--l",
     isPageHeading = true
   ))
 )),
 items = Seq(
   RadioItem(
     content = Text("England"),
     value = Some("england")
   ),
   RadioItem(
     content = Text("Scotland"),
     value = Some("scotland")
   ),
   RadioItem(
     content = Text("Wales"),
     value = Some("wales")
   ),
   RadioItem(
     content = Text("Northern Ireland"),
     value = Some("northern-ireland")
   )
 )
).withFormField(myForm("whereDoYouLive")))  /* wires up things like checked status of inputs from a play form field */

Handling user input securely

Cross-site scripting (XSS) is an attack where a malicious actor executes arbitrary JavaScript in the user's browser, typically to exfiltrate sensitive data such as cookies or session state, by including <script> tags, or attributes like onload that can execute JavaScript. There are a few ways that you can protect your service from these sorts of attack.

Content-Security Policy (CSP)

Disabling the use of inline JavaScript, by removing 'unsafe-inline' from your CSP, will reduce the risk of injected JavaScript from running.

Sanitising or rejecting user input on submission

The best way to protect your service against malicious input is to sanitise or reject it as soon as it's submitted. This might be via a form submission, or as path/query parameters in a URL. Such data should be validated against the most restrictive constraints possible.

Within the Play framework, this can be achieved using custom form mappings or request binders. eg. for Forms:

    val myForm = Form[MyData](
      mapping(
        "username" -> Forms.text.verifying(_.matches("""^[^<>"&]*$""")) // This will reject XSS chars
      )(MyData.apply)(MyData.unapply)
    )

Escaping dynamic data when rendering HTML

Even if data comes from a trusted API, it may have got there via an insecure route, so it should always be treated as unsafe. When including any dynamic data in HTML pages, it should be escaped.

In Twirl templates, including user data with dynamic statements (@ notation) is automatically escaped by Play.

When passing data values to components in play-frontend-hmrc, you should use one of the types derived from the Content trait:

eg.

    SummaryListRow(
      value = Value(Text(myDataValue))
    )

Messages

It is worth specifically mentioning the use of HTML in messages. Where possible, restrict the use of HTML tags to Twirl templates, and use messages for plain text. If messages contain HTML, use @Messages to render them inline, or wrap them with HtmlContent when passing to a Scala component. Be extra careful if messages include user-provided data as parameters.

eg.

messages.conf:

    some.text.message=Welcome {0}
    some.html.message=<b>Welcome {0}</b>

Twirl template:

    @* Safe - auto-escaped by Play *@
    <b>@Messages("text.message", username)</b>

    @* Unsafe - HtmlContent used since message contains HTML, but leads to dangerous use of username *@
    @govUkNotificationBanner(NotificationBanner(content = HtmlContent(Messages("html.message", username))))

Useful implicits

The following imports will summon implicit classes that include extension methods for HTML trims, pads, indents, handling HTML emptiness and wiring Play forms.

@import uk.gov.hmrc.govukfrontend.views.html.components.implicits._
@import uk.gov.hmrc.hmrcfrontend.views.html.components.implicits._

withFormField

An extension method withFormField(field: play.api.data.Field) exists for the following classes:

This method allows a Play forms Field to be passed through when creating an instance of a form input, which will enrich the input with the following:

The methods can be used as methods in a Twirl template as demonstrated below:

@import uk.gov.hmrc.govukfrontend.views.html.components._
@import uk.gov.hmrc.govukfrontend.views.html.components.implicits._

@this(govukInput)

@(label: String, field: Field)(implicit messages: Messages)

@govukInput(
  Input(
    label = Label(classes = labelClasses, content = Text(label))
  ).withFormField(field)
)

If a value is passed though to the input .apply() method during construction, it will NOT be overwritten by the Field values. These are only used if the object parameters are not set to the default parameters.

Note that you will need to pass through an implicit Messages to your template.

Additionally, there is a second method withFormFieldWithErrorAsHtml(field: play.api.data.Field) which behaves as the withFormField method with the difference that form errors are bound as instances of HtmlContent.

withHeading and withHeadingAndCaption

Extension methods withHeading(heading: Content) and withHeadingAndSectionCaption(heading: Content, sectionCaption: Content) exist for the following classes:

These methods allow either Text or HtmlContent content to be passed through to the form inputs and set as either label or fieldset legend, depending on the underlying component. The methods will also concatenate and apply styling for the content as below:

Parameter Styling applied
heading for Legend <h1 class="govuk-fieldset__heading hmrc-page-heading govuk-!-margin-top-0 govuk-!-margin-bottom-0">$content</h1>
heading for Label govuk-label--xl hmrc-page-heading govuk-!-margin-top-0 govuk-!-margin-bottom-2
sectionCaption <span class="govuk-caption-xl hmrc-caption-xl">$sectionCaption</span>

RichDateInput

The implicit class RichDateInput provides three extension methods to populate the DateInput view model:

These methods takes a Play play.api.data.Field and enrich the DateInput with:

These methods will throw an IllegalArgument exception if passed a pre-populated set of DateInput items. These methods should only be used to create standard empty items. If your use case is more complex, you may need to construct your own DateInput.

For example, if using the one question per page pattern, the method could be used as follows:

@govukDateInput(DateInput(
  hint = Some(Hint(content = Text("date.hint"))), 
  fieldset = Some(Fieldset(
    legend = Some(Legend(
      content = Text(messages("date.heading")), 
      classes = "govuk-fieldset__legend--l", isPageHeading = true)))
  )).withDayMonthYearFormField(dateInputForm("date")))  

Setting up form validation for this field might look like:

case class DateData(day: String, month: String, year: String)  
case class PageData(date: DateData)  

object DateFormBinder {  
 def form: Form[PageData] = Form[PageData](mapping(
   "date" -> mapping(
     "day" -> text.verifying(dayConstraint),
     "month" -> text.verifying(monthConstraint),
     "year"  -> text.verifying(yearConstraint)
   )(DateData.apply)(DateData.unapply)
     .verifying(dateConstraint) )(PageData.apply)(PageData.unapply) )}  

In the code above, dayConstraint, monthConstraint, yearConstraint and dateConstraint would be defined
as per the Play documentation on custom
validations.

The controller submit method for this form might look like:

def submit() = Action { implicit request =>  
 DateFormBinder.form .bindFromRequest() 
   .fold( 
     formWithError => BadRequest(dateInputPage(formWithError, routes.DateInputController.submit())),
     _ => Redirect(routes.DateInputController.thanks()) 
   )}  

Additionally, there are methods withDayMonthYearFormFieldWithErrorAsHtml(field: Field), withDayMonthFormFieldWithErrorAsHtml(field: Field) and withMonthYearFormFieldWithErrorAsHtml(field: Field) which behave as the above methods with the difference that form errors are bound as instances of HtmlContent.

Note that you will need to pass through an implicit Messages to your template.

RichErrorSummary

The implicit class RichErrorSummary provides extension methods withFormErrorsAsText and withFormErrorsAsHtml to hydrate an ErrorSummary with the standard 'There is a problem' title in English and Welsh and the errors found in a Play form.

If your form is simple with no nested field values, and you are using the withFormField extension methods to hydrate your form inputs, the helper can be used simply as follows:

@if(form.errors.nonEmpty) {
    @govukErrorSummary(ErrorSummary().withFormErrorsAsText(form))
}

This will construct an ErrorSummary with errors hyperlinked according to the form error keys. For example an error on the name field will be linked to its corresponding input via the href #name. This relies on each HTML input having their id set to their field name.

If you have a form with nested field values and are performing validation on composite fields, such as in the DateInput example above, you will need to map any errors on the composite field to the field corresponding to the first HTML input element in the group:

@if(form.errors.nonEmpty) {
    @govukErrorSummary(ErrorSummary().withFormErrorsAsText(form, mapping = Map("date" -> "date.day")))
}

This will ensure all errors are clickable is consistent with GDS guidance

Note, these methods will not overwrite any existing ErrorSummary properties. For example, if you manually pass in a non-empty title, it will not be overwritten.

To use this class you will need to have an implicit Messages in scope.

RichStringSupport

The implicit class RichStringSupport hydrates the basic String class with a series of extension helper methods to convert a String to a component without the need for nesting in case classes, or for wrapping as Text.

These methods are called as standard extension methods, for example:

import uk.gov.hmrc.govukfrontend.views.html.components.implicits._

"This is a string for a fieldset legend".toFieldset

will return:

Fieldset(
  legend = Some(Legend(content = Text("This is a string for a fieldset legend")))
)

The following implicit conversions exist for a String:

Creating HMRC-style pages

Using the HMRC standard page template

The HmrcStandardPage helper generates a standard HMRC page layout including the HmrcStandardHeader, HmrcStandardFooter, Welsh language toggle, and various banners. This helper takes HmrcStandardPageParams which includes the following members:

To use this component,

  1. Create a custom layout template Layout.scala.html to suit your service's needs, for example:
@import uk.gov.hmrc.hmrcfrontend.views.config.StandardBetaBanner
@import uk.gov.hmrc.hmrcfrontend.views.html.helpers.HmrcStandardPage
@import uk.gov.hmrc.hmrcfrontend.views.viewmodels.layout._
@import config.AppConfig
@import uk.gov.hmrc.anyfrontend.controllers.routes

@this(hmrcStandardPage: HmrcStandardPage, standardBetaBanner: StandardBetaBanner)

@(pageTitle: String, appConfig: AppConfig)(contentBlock: Html)(implicit request: RequestHeader, messages: Messages)

@hmrcStandardPage(
  HmrcStandardPageParams(
    serviceURLs = ServiceURLs(
      serviceUrl = Some(routes.IndexController.index().url),
      signOutUrl = Some(routes.SignOutController.signOut().url)
    ),
    banners = Banners(
      phaseBanner = Some(standardBetaBanner(url = appConfig.betaFeedbackUrl))
    ),
    serviceName = serviceName,
    pageTitle = pageTitle,
    isWelshTranslationAvailable = true /* or false if your service has not been translated */
  )(contentBlock)

The parameters that can be passed into the hmrcStandardPage are as follows:

  | Parameter                                  | Description                                                       | Example                                                     |
  | ------------------------------------------ | ----------------------------------------------------------------- | ----------------------------------------------------------- |
  | `service.serviceUrl`                       | This will be bound to hmrcStandardHeader                          | `Some(routes.IndexController.index().url)`                  |
  | `service.signOutUrl`                       | Passing a value will display the sign out link                    | `Some(routes.SignOutController.signOut().url)`              |
  | `service.accessibilityStatementUrl`        | Passing a value will override the accessibility statement URL in the [footer](#accessibility-statement-links)                  ||
  | `banners.displayHmrcBanner`                | Setting to true will display the [HMRC banner](https://design.tax.service.gov.uk/hmrc-design-patterns/hmrc-banner/)            ||
  | `banners.phaseBanner`                      | Passing a value will display alpha or beta banner.                | `Some(standardBetaBanner())`                                |
  | `banners.userResearchBanner`               | Passing a value will display the user research banner             | `Some(UserResearchBanner(url = appConfig.userResearchUrl))` |
  | `banners.additionalBannersBlock`           | Pass extra html into the header, intended for custom banners.     | `Some(attorneyBanner)`                                      |
  | `templateOverrides.additionalHeadBlock`    | Passing a value will add additional content in the HEAD element   |                                                             |
  | `templateOverrides.additionalScriptsBlock` | Passing a value will add additional scripts at the end of the BODY|                                                             |
  | `templateOverrides.beforeContentBlock`     | Passing a value will add content between the header and the main element. This content will override any `isWelshTranslationAvailable`, `backLink` or `backLinkUrl` parameter.||
  | `templateOverrides.mainContentLayout`      | Passing value will override the default two thirds layout         |                                                             |
  | `templateOverrides.pageLayout`             | Allow internal services to use a full width layout.               | `Some(fixedWidthPageLayout(_))`                             |
  | `templateOverrides.headerContainerClasses` | Allow internal services to use a full width header.               | `"govuk-width-container"`                                   |
  | `serviceName`                              | Pass a value only if your service has a dynamic service name      |                                                             |
  | `pageTitle`                                | This will be bound to govukLayout                                 |                                                             |
  | `isWelshTranslationAvailable`              | Setting to true will display the language toggle                  | `true`                                                      |
  | `backLink`                                 | Passing a value will display a back link                          | `Some(BackLink(href = ..., attributes = ...))`              |
  | `exitThisPage`                             | Passing a value will display an "Exit This Page" button           | `Some(ExitThisPage())`                                      |

Creating consistent page headings

[!WARNING] The hmrc guidance for creating headings with a section (caption) has recently changed. The following helpers are still available but this is no longer the recommended approach. Consult the linked documentation for examples of the new recommendation.

The HmrcPageHeadingLabel and HmrcPageHeadingLegend helpers let you use a label or legend as a page heading with a section (caption) displayed above it.

For example, how you could use HmrcPageHeadingLabel with a GovukInput:

@import uk.gov.hmrc.govukfrontend.views.html.components.{GovukInput, Input, Hint, Text, Text}
@import uk.gov.hmrc.govukfrontend.views.html.components.implicits._
@import uk.gov.hmrc.hmrcfrontend.views.config.{HmrcPageHeadingLabel, HmrcSectionCaption}

@this(govukInput: GovukInput)

@(myForm: Form[_])(implicit messages: Messages)

@govukInput(
  Input(
    label = HmrcPageHeadingLabel(content = Text("What is your name?"), caption = HmrcSectionCaption(Text("Personal details"))),
    hint = Some(Hint(content = Text("This example shows a page heading inside a <label>")))
  ).withFormField(myForm("whatIsYourName"))
)

For example, how you could use HmrcPageHeadingLegend with GovukRadios:

@import uk.gov.hmrc.govukfrontend.views.html.components.{Radios, RadioItem, Fieldset, Hint, Text}
@import uk.gov.hmrc.govukfrontend.views.html.components.implicits._
@import uk.gov.hmrc.hmrcfrontend.views.config.HmrcPageHeadingLegend

@this(govukInput: GovukRadios)

@(myForm: Form[_])(implicit messages: Messages)

@govukRadios(Radios(
  fieldset = Some(Fieldset(
    legend = Some(HmrcPageHeadingLegend(content = Text("Where do you live?"), caption = HmrcSectionCaption(Text("Personal details")))
  )),
  hint = Some(Hint(content = Text("This example shows a page heading inside a <legend>"))),
  items = List("England", "Scotland", "Wales", "Northern Ireland") map { place =>
    RadioItem(
      content = Text(place),
      value = Some(place)
    )
  }).withFormField(myForm("whereDoYouLive")))

Adding a sidebar to your Layout

The HmrcStandardPage by default renders the main contentBlock of the page in two thirds width content.

However, if you wish to override the styling of the main content, you can do so by passing in an optional mainContentLayout parameter of type Option[Html => Html], which will apply wrapping content around your contentBlock.

If you wish to create a layout with two thirds, one third styling (for example if your page has a sidebar), there is a helper TwoThirdsOneThirdMainContent.scala.html which can be used as follows:

@import uk.gov.hmrc.govukfrontend.views.html.components.TwoThirdsOneThirdMainContent
@import uk.gov.hmrc.hmrcfrontend.views.html.helpers.HmrcStandardPage
@import uk.gov.hmrc.hmrcfrontend.views.viewmodels.hmrcstandardpage._

@this (
  hmrcStandardPage: HmrcStandardPage,
  twoThirdsOneThirdMainContent: TwoThirdsOneThirdMainContent
)

@(pageTitle: String, isWelshTranslationAvailable: Boolean = true)(contentBlock: Html)(implicit request: RequestHeader, messages: Messages)

@sidebar = {
  <h2 class="govuk-heading-xl">This is my sidebar</h2>
  <p class="govuk-body">There is my sidebar content</p>
}

@hmrcStandardPage(
  banners = Banners(displayHmrcBanner = true),
  templateOverrides = TemplateOverrides(
    mainContentLayout = Some(twoThirdsOneThirdMainContent(sidebar))
  ),
  pageTitle = Some(pageTitle),
  isWelshTranslationAvailable = isWelshTranslationAvailable
)(contentBlock)

Alternatively, you can declare any template and pass it through as a function or partially applied function that has the signature Html => Html.

For example, you can add a template WithSidebarOnLeft.scala.html as below:

@this()

@(sidebarContent: Html)(mainContent: Html)

<div class="govuk-grid-row">
    <div class="govuk-grid-column-one-third">
        @sidebarContent
    </div>
    <div class="govuk-grid-column-two-thirds">
      @mainContent
    </div>
</div>

You can then inject this into your Layout.scala.html and partially apply the function as above.

[!WARNING] FullWidthPageLayout should only be used by internal services. The default fixed width layout should be used for all public services.

Finding working examples

You can find working examples of the use of play-frontend-hmrc in the following actively maintained repositories:

Integrating with shared HMRC services

Adding a beta feedback banner

If you would like to add a banner to your service stating that your service is in beta, and providing a link to a feedback form, you can do so use the StandardBetaBanner viewmodel to construct a PhaseBanner, which is bound to a GovukPhaseBanner Twirl template.

The HmrcStandardPage, HmrcStandardHeader and HmrcStandardHeader all have constructor methods that take in an optional PhaseBanner and then bind to the appropriate template.

For developers wanting to implement a beta feedback banner in your service, these steps should be followed:

  1. Inject an instance of StandardBetaBanner into your template
  2. Call the apply method with either:
    1. An explicit url parameter, OR
    2. An implicit instance of ContactFrontendConfig injected into your template

If you pass an implicit, injected instance of ContactFrontendConfig to your StandardBetaBanner, then the beta feedback banner will be bound with a URL pointing to beta feedback form in the contact-frontend microservice, together with URL parameters referrerUrl and service.

As with Helping users report technical issues, add the following to your application.conf:

contact-frontend.serviceId = "<any-service-id>"

You can then use the banner as per below (note the injected implicit ContactFrontendConfig):

@import uk.gov.hmrc.hmrcfrontend.config.ContactFrontendConfig
@import uk.gov.hmrc.hmrcfrontend.views.config.StandardBetaBanner
@import uk.gov.hmrc.hmrcfrontend.views.html.helpers.HmrcStandardPage
@import uk.gov.hmrc.hmrcfrontend.views.viewmodels.hmrcstandardpage._
@import config.AppConfig
@import uk.gov.hmrc.anyfrontend.controllers.routes

@this(hmrcStandardPage: HmrcStandardPage, standardBetaBanner: StandardBetaBanner)(implicit cfConfig: ContactFrontendConfig)

@(pageTitle: String, appConfig: AppConfig)(contentBlock: Html)(implicit request: RequestHeader, messages: Messages)

@hmrcStandardPage(
  serviceURLs = ServiceURLs(
    serviceUrl = Some(routes.IndexController.index().url),
    signOutUrl = Some(routes.SignOutController.signOut().url)
  ),
  banners = Banners(phaseBanner = Some(standardBetaBanner())),
  pageTitle = Some(pageTitle),
  isWelshTranslationAvailable = true /* or false if your service has not been translated */
)(contentBlock)

Adding a User Research Banner

The User Research Banner is a component used to display a blue banner, containing link text inviting the service user to take part in user research. The Twirl template is HmrcUserResearchBanner.scala.html, and the viewmodel is UserResearchBanner.scala.

The banner contains hard coded content, available in English and Welsh, with translation handled automatically via the Play language from an implicit request. It is not possible to change this content, as it has been provided by Research Services, and needs to be consistent across tax.service.gov.uk.

If your service uses HmrcStandardPage.scala.html, you can add the HmrcUserResearchBanner as shown:

@hmrcStandardPage(
  serviceURLs = ???,
  banners = Banners(userResearchBanner = Some(UserResearchBanner(url = "your-user-research-url-here")))
  pageTitle = ???
)(contentBlock)

Research Services will tell you what URL to use for your service.

Linking to your accessibility statement

hmrcStandardFooter, included as part of hmrcStandardPage, generates the standard GOV.UK footer including the standardised list of footer links for tax services.

To configure this helper to link to the accessibility statement service, provide the key accessibility-statement.service-path in your application.conf file. This key is the path to your accessibility statement under https://www.tax.service.gov.uk/accessibility-statement.

For example, if your accessibility statement is https://www.tax.service.gov.uk/accessibility-statement/discounted-icecreams, this property must be set to /discounted-icecreams as follows:

accessibility-statement.service-path = "/discounted-icecreams"

In the exceptional case that you need to link to an accessibility statement not hosted within accessibility-statement-frontend, the default behaviour can be overridden by supplying an accessibilityStatementUrl parameter to hmrcStandardFooter.

Helping users report technical issues

The hmrcReportTechnicalIssueHelper component generates a link that allows users to report technical issues with your service.

To configure this helper, add the following configuration to your application.conf

contact-frontend.serviceId = "<any-service-id>"

serviceId helps identify your service when members of the public report technical issues. If your service is not already integrating with contact-frontend, we advise choosing an identifier that is specific to your service and unlikely to be used by any other service, avoiding any special characters or whitespace.

The component should be added to the bottom of each page in your service. This can be done by defining a reusable block in your layout template and passing into hmrcStandardPage or govukLayout in place of contentBlock:

@content = {
  @contentBlock
  @hmrcReportTechnicalIssueHelper()
}

Allowing users to enable or disable tracking cookies

Integrating with tracking consent

If you intend to use Google Analytics or Optimizely to measure usage of your service, you will need to integrate with tracking-consent-frontend. The hmrcHead helper generates the necessary HTML SCRIPT tags that must be injected into the HEAD element for every page on your service provided it is configured correctly as below.

Before integrating, it is important to remove any existing snippets relating to GTM, GA or Optimizely. If they are not removed there is a risk the user’s tracking preferences will not be honoured correctly.

Configure your service’s GTM container in conf/application.conf. For example, if you have been instructed to use GTM container a, the configuration would appear as:

tracking-consent-frontend {
  gtm.container = "a"
}

gtm.container can be one of: a, b, c, d, e, f or sdes. Consult with the CIPSAGA team to identify which GTM container you should be using in your service.

Adding GTM to internal services

If you would like to add GTM to an internal service, you can do so using the HmrcInternalHead helper, which will add the GTM snippet in the <head> block. It should be used as demonstrated below in your own Layout.scala.

@import uk.gov.hmrc.govukfrontend.views.html.components.GovukLayout
@import uk.gov.hmrc.hmrcfrontend.views.html.helpers.HmrcInternalHead
@import uk.gov.hmrc.hmrcfrontend.views.html.components.HmrcInternalHeader
@import uk.gov.hmrc.hmrcfrontend.views.viewmodels.internalheader.InternalHeader

@this(
        govukLayout: GovukLayout,
        hmrcInternalHead: HmrcInternalHead,
        hmrcInternalHeader: HmrcInternalHeader
)
@(pageTitle: Option[String] = None)(contentBlock: Html)(implicit request: Request[_], messages: Messages)

@govukLayout(
  pageTitle = pageTitle,
  headBlock = Some(hmrcInternalHead()),
  headerBlock = Some(hmrcInternalHeader(InternalHeader()))
)(contentBlock)

Using common HMRC patterns

Adding a dynamic character count to a text input

hmrcCharacterCount is an implementation of the GOV.UK CharacterCount that translates the dynamic words / characters remaining text into English or Welsh using the Play framework Message API. You do not need to pass through the language explicitly to this component, just pass through an implicit Messages.

@import uk.gov.hmrc.hmrcfrontend.views.html.components._
@import uk.gov.hmrc.hmrcfrontend.views.html.components.implicits._

@this(hmrcCharacterCount: HmrcCharacterCount)

@(label: String, maxWords: Int, field: Field)(implicit messages: Messages)

@hmrcCharacterCount(CharacterCount(
    label = Label(content = Text(label)),
    maxWords = Some(maxWords)
)).withFormField(field)

Adding accessible autocomplete to a select input

play-frontend-hmrc contains Javascript and CSS assets to enable <select> items with autocomplete to improve their accessibility. Currently, this is pulling in the Javascript and CSS from the GOV.UK library accessible-autocomplete.

If you are currently using accessible-autocomplete, you can replace your components and routing by using the ones in this library.

You will need to inject the CSS and Javascript into your views as follows:

@import uk.gov.hmrc.hmrcfrontend.views.html.helpers.HmrcAccessibleAutocompleteCss
@import uk.gov.hmrc.hmrcfrontend.views.html.helpers.HmrcAccessibleAutocompleteJavascript
@import uk.gov.hmrc.hmrcfrontend.views.html.helpers.HmrcStandardPage
@import uk.gov.hmrc.hmrcfrontend.views.viewmodels.hmrcstandardpage._

@this (
  hmrcStandardPage: HmrcStandardPage,
  autocompleteCss: HmrcAccessibleAutocompleteCss,
  autocompleteJavascript: HmrcAccessibleAutocompleteJavascript
)

@(pageTitle: String, contentBlock: Html)(implicit request: RequestHeader, messages: Messages)

@hmrcStandardPage(
  banners = Banners(displayHmrcBanner = true),
  templateOverrides = TemplateOverrides(
    additionalHeadBlock = Some(autocompleteCss()),
    additionalScriptsBlock = Some(autocompleteJavascript())
  ),
  pageTitle = Some(pageTitle)
)(contentBlock)

References within your code to the Javascript object accessibleAutocomplete should be replaced with HMRCAccessibleAutocomplete.

Transforming a <select> element into an Accessible Autocomplete element

With the above accessible-autocomplete CSS and Javascript, you can transform your Select element into an AccessibleAutocomplete element using the asAccessibleAutocomplete implicit helper method on a Select, as follows.

@import uk.gov.hmrc.govukfrontend.views.Implicits.RichSelect

@govukSelect(Select(...).asAccessibleAutocomplete())

This will set up an accessible autocomplete element with default values set as below

defaultValue = ""
showAllValues = false
autoselect = false

More information on these values can be found here under the API documentation heading

If you wish to change these values you can do so by providing an AccessibleAutocomplete model into the asAccessibleAutocomplete method as seen below

@govukSelect(Select(...).asAccessibleAutocomplete(Some(
  AccessibleAutocomplete(
    defaultValue = Some("United Kingdom"),
    showAllValues = true,
    autoSelect = true)
)))

There is a caveat currently with the defaultValue property, to use this option you will need to ensure that you have a placeholder item in your select element that has an empty value. You will also need to make sure none of your select items have the selected property set on them. See example below.

@govukSelect(Select(
        id = "sort",
        name = "sort",
        items = Seq(
            SelectItem(
                text = "Placeholder text"
            ),
            SelectItem(
                value = Some("published"),
                text = "Recently published"
            ),
            SelectItem(
                value = Some("updated"),
                text = "Recently updated"
            )
        ),
        label = Label(
            content = Text("Sort by")
        )
    ).asAccessibleAutocomplete(Some(
        AccessibleAutocomplete(
            defaultValue = Some("Recently updated"),
            showAllValues = true,
            autoSelect = false)
    )))

More information on defaultValue property can be found here under the Null options heading

A preferred way would be to select a default value using the selected attribute on an select item instead of using the defaultValue property, as seen below.

@govukSelect(Select(
        id = "sort",
        name = "sort",
        items = Seq(
            SelectItem(
                value = Some("published"),
                text = "Recently published"
            ),
            SelectItem(
                value = Some("updated"),
                text = "Recently updated"
                selected = true
            )
        ),
        label = Label(
            content = Text("Sort by")
        )
    ).asAccessibleAutocomplete(Some(
        AccessibleAutocomplete(
            showAllValues = true,
            autoSelect = false)
    )))

Warning users before timing them out

In order to meet the accessibility WCAG 2.1 Principle 2: Operable you must provide users with enough time to read and use content. In particular, WCAG 2.1 Success Criterion 2.2.1 (Timing Adjustable) requires that users are able to turn off, adjust or extend the time limit, giving them at least 20 seconds to perform this with a simple action.

On MDTP, users are, by default, automatically timed out of any authenticated service after 15 minutes of inactivity. This mechanism, implemented in SessionTimeoutFilter, clears all non-allow-listed session keys after the timeout duration has elapsed. Services can override this default by adjusting the session.timeout configuration key in conf/application.conf.

The hmrcTimeoutDialogHelper component helps services meet this accessibility obligation by delivering an accessible timeout warning inside a modal dialog a configurable number of seconds before they are due to be timed out. The dialog warns the user with the message 'For your security, we will sign you out in X minutes.' which is updated every minute until 60 seconds are remaining, at which point it counts down in seconds. For screen-reader users, an audible message counts down in 20 second increments.

Users are then given the option to 'Stay signed in', which extends their session by the timeout duration, or 'Sign out' returning them to the supplied signOutUrl.

Integrating with the timeout dialog

The instructions below assume you have set up play-frontend-hmrc as indicated above.

  1. Identify the signOutUrl that should be used when users click 'Sign Out' on the timeout dialog. Your service may already be supplying a signOutUrl parameter to the hmrcStandardHeader component, which controls the sign out link in the GOV.UK header. Reusing this value may be a sensible choice. Refer to guidance above to understand how this argument is used by the timeout dialog.

  2. Update your layout template to pass in the hmrcTimeoutDialogHelper in the HEAD element, supplying the signOutUrl as a parameter. For example if using hmrcStandardPage, pass Some(hmrcTimeoutDialogHelper(signOutUrl = signOutUrl)) in the templateOverrides.additionalHeadBlock parameter.

Synchronising session between tabs

By default, play-frontend-hmrc synchronises session extension between different HMRC tabs (using the BroadcastChannel API in browsers). In practical terms, this means that if a user sees a timeout dialog in an active tab, and clicks to extend their session, then the timeout dialogs that have also opted into this behaviour in any background tabs will also restart the countdowns until they display their timeout warning.

This behaviour is currently flagged on (true) by default. To disable, you can either explicitly pass Some(false) to the HmrcTimeoutDialogHelper, or you can add a boolean false to your application.conf with the key hmrc-timeout-dialog.enableSynchroniseTabs.

Customising the timeout dialog

By default, the timeout dialog will redirect to the supplied signOutUrl if they do nothing after the timeout duration has elapsed. If you wish users to be redirected to a different URL, a separate timeoutUrl can be supplied.

If your service has a timeout duration different to that configured in the session.timeout configuration key used by bootstrap-play, it can be overridden using the timeout parameter. Likewise, the number of seconds warning can be adjusted using the countdown parameter.

If you need to perform special logic to keep the user’s session alive, the default keep alive mechanism can be overridden using the keepAliveUrl parameter. This must be a side effect free endpoint that implements HTTP GET and can be called via an XHR request from the timeout dialog Javascript code. A good practice is to have a dedicated controller and route defined for this so its use for this purpose is explicit. This url will be supplied in the keepAliveUrl parameter to hmrcTimeoutDialog. Do not use # in case the current URL does not implement HTTP GET.

Parameter Description Example
signOutUrl The url that will be used when users click 'Sign Out' Some(routes.SignOutController.signOut().url)
timeoutUrl The url that the timeout dialog will redirect to following timeout. Defaults to the signOutUrl. Some(routes.TimeoutController.timeOut().url)
keepAliveUrl A endpoint used to keep the user’s session alive Some(routes.KeepAliveController.keepAlive().url)
timeout The timeout duration where this differs from session.timeout 1800
countdown The number of seconds before timeout the dialog is displayed. The default is 120. 240
synchroniseTabs Allow the timeout dialog to use the BroadcastChannel to communicate session activity to other background tabs. Defaults to Some(true), i.e. enabled. di Some(true)

The timeout dialog’s content can be customised using the following parameters:

Parameter Description Example
title The text to use as a title for the dialog Some(messages("hmrc-timeout-dialog.title"))
message The message displayed to the user Some(messages("hmrc-timeout-dialog.message"))
messageSuffix Any additional text to be displayed after the timer Some(messages("hmrc-timeout-dialog.message-suffix"))
keepAliveButtonText The text on the button that keeps the user signed in Some(messages("hmrc-timeout-dialog.keep-alive-button-text"))
signOutButtonText The text for the link which takes the user to a sign out page Some(messages("hmrc-timeout-dialog.sign-out-button-text"))

Opening links in a new tab

The HmrcNewTabLinkHelper component allows you to link to content that opens in a new tab, with protection against reverse tabnapping. It takes in an implicit Messages parameter to translate the content (opens in new tab).

It is a wrapper around the HmrcNewTabLink, however this helper means that services do not need to explicitly pass in a language for internationalization of the link text.

It can be used as follows:

import uk.gov.hmrc.hmrcfrontend.views.html.helpers.HmrcNewTabLinkHelper
import uk.gov.hmrc.hmrcfrontend.views.viewmodels.newtablinkhelper.NewTabLinkHelper

@this(hmrcNewTabLinkHelper: HmrcNewTabLinkHelper)
@(linkText: String, linkHref: String)(implicit messages: Messages)

@hmrcNewTabLinkHelper(NewTabLinkHelper(
  text = linkText,
  href = Some(linkHref)
))

Adding an "Exit this page" button

The GovukExitThisPage component will add to the page a red "sticky" button saying "Exit this page" or custom text, which when clicked will redirect the user to another website (default is set to www.bbc.co.uk/weather). This component has been designed to help users viewing sensitive information that could put them at risk. For example, when a potential victim is using a service to help them leave a domestic abuser. More information about when to use this component can be found in the GOV.UK Design System.

To ensure the button is correctly positioned on the page, use the HmrcStandardPage component and pass through an ExitThisPage case class. If you are using the default content as below, the button text will automatically be translated if the user is viewing the Welsh version of the page.

@import uk.gov.hmrc.hmrcfrontend.views.html.helpers.HmrcStandardPage
@import uk.gov.hmrc.hmrcfrontend.views.viewmodels.layout._
@import config.AppConfig

@this(hmrcStandardPage: HmrcStandardPage)

@(pageTitle: String, appConfig: AppConfig)(contentBlock: Html)(implicit request: RequestHeader, messages: Messages)

@hmrcStandardPage(
  HmrcStandardPageParams(
    ....
    exitThisPage = Some(ExitThisPage()) /* custom parameters can be passed in such as redirectUrl or button text */
  )(contentBlock)

If you are not using the GOV.uk standard two-thirds width layout, please note that the sticky button may overlay your main content when scrolled. For example, it will overlay a sidebar or full-width layout. Services with wider main content may wish to carry out additional testing before deciding whether to implement this component.

Advanced configuration

Adding your own SASS compilation pipeline

This library will manage SASS compilation for you. However, should you need for any reason to add your own SASS compilation pipeline, follow the steps detailed here.

Configuring non-HMRC projects to resolve play-frontend-hmrc artefacts

HMRC services get this configuration via the sbt-auto-build library, external consumers will need to add the repository below to their SBT config themselves:

resolvers += MavenRepository("HMRC-open-artefacts-maven2", "https://open.artefacts.tax.service.gov.uk/maven2")

Using the Tudor Crown on GOV.UK and HMRC components

As of February 2024, there is a requirement for Government departments to use the Tudor Crown logo for HRH King Charles III. This new logo has been added into both the govuk-frontend and hmrc-frontend libraries. Additionally, the hmrc-frontend library has an updated HMRC Crest roundel incorporating the Tudor Crown.

The Tudor Crown is available, and shown by default, in v8.5.0 and higher of play-frontend-hmrc.

Getting help

Please report any issues with this library in Slack at #team-plat-ui.

Troubleshooting

If you are adding HTML elements to your page such as <h1> or <p>, you will need to add the CSS classes for the GDS Transport fonts from the GOV.UK Design System. A full list of the CSS classes can be found at https://design-system.service.gov.uk/styles/type-scale/.

These styles have already been applied to the components supplied in play-frontend-hmrc, but you will need to manually add the styles to your service's own HTML elements.

Useful Links

Owning team README

Rationale for code and translation decisions, dependencies, as well as instructions for team members maintaining this repository can be found here.

License

This code is open source software licensed under the Apache 2.0 License.