Skip to main content

Building your own JavaScript components

To help you build and initialise your own JavaScript components, GOV.UK Frontend provides some of its internal features for you to reuse:

Using GOV.UK Frontend’s features means you will not have to redevelop these features yourself, and it reduces the size of the bundled JavaScript.

Using the Component class

JavaScript components in GOV.UK Frontend share a number of behaviours. They:

  • check that GOV.UK Frontend is supported
  • check that the component is not already initialised on its root element
  • store the root element of the component

The Component class implements these behaviours in an extensible way, so you can write code for your component that focuses on its specific behaviour.

Defining static properties on components

Some of the Component’s features are supported by static properties on the component that inherit from it. However, native static properties are not a JavaScript syntax supported by all browsers that support <script type=”module”>.

If your project’s tools are able to convert the syntax to code supported by browsers that support <script type=”module”>, you can write these properties as native static ones in your component’s class:

class MyComponent extend Component{
  static property = value
}

Otherwise, you can add these properties to your component’s class after its definition:

class MyComponent extends Component {}
MyComponent.property = value

Defining the moduleName property

Components extending the Component class need to define a moduleName static property:

class MyComponent extend Component{
  static moduleName = app-my-component
}

Doing this helps:

Customising initialisation of components

When your component is initialised, it’ll need to perform some tasks, such as adding event listeners. You can add these tasks to your component’s constructor after calling super to set up the base Component’s default behaviour.

class MyComponent extends Component {
  static moduleName = app-my-component

  constructor($root) {
    super($root)

    // Run component specific initialisation here for example
    this.$root.addEventListener(click, this.handleClick.bind(this))
  }

  handleClick(event) {
    // Handle click inside the component’s root
  }
}

Accessing the root element in the component

Within the component’s methods and its constructor, you can access the root element that the component was initialised on using this.$root.

class MyComponent extends Component {
  static moduleName = app-my-component

  constructor($root) {
    super($root)

    // Run component specific initialisation here, for example:
    this.$root.addEventListener(click, this.handleClick.bind(this))
  }

  handleClick(event) {
    this.$root.classList.add(app-my-component--clicked)
  }
}

Customising the expected type of the root element

The Component class will verify that the root element is of the right type (by default, HTMLElement). You can set an elementType static property on your component to define a specific type to check against:

/**
 * @extends Component<HTMLAnchorElement> 
 */
class LinkEnhancement extends Component {
  static moduleName = app-link-enhancement

  // This will make sure that the component can only be initialised
  // on HTMLAnchorElement
  static rootElementType = HTMLAnchorElement

  constructor($root) {
    super($root);

    // this.$root is an `HTMLAnchorElement` and you can access specific properties
    // like `href` or `hash`
  }
}

Using this.$root with TypeScript

As govuk-frontend does not provide type definitions in its package, TypeScript might show the following error message:

Property ‘$root’ does not exist on type <NAME_OF_YOUR_CLASS>.

You can work around this issue by defining the ‘$root’ property in your component class yourself.

class MyComponent extends Component {
  static moduleName = app-my-component

  // Defining the property here makes TypeScript aware of its existence
  // saving from using `@ts-expect-error` each time you'd access `this.$root`
  get $root() {
    // Unfortunately, govuk-frontend does not provide type definitions
    // so TypeScript does not know of `this._$root`
    // @ts-expect-error
    return this._$root;
  }

  constructor($root) {
    super($root)

    // Run component specific initialisation here, for example:
    this.$root.addEventListener(click, this.handleClick.bind(this))
  }

  handleClick(event) {
    this.$root.classList.add(app-my-component--clicked)
  }
}

If you’re interested in better TypeScript support for GOV.UK Frontend, let us know on the GitHub issue for exporting type declarations.

Customising how support is checked

The Component class will check that GOV.UK Frontend is supported during initialisation. However, your component may have different requirements for running properly. For example, it may require specific JavaScript APIs that are not supported by all browsers.

You can redefine the static checkSupport method so the component throws an error if these requirements are not met. We recommend adding the component’s moduleName to help error messages identify which component they relate to without having to search their stack trace.

class MyComponent extends Component {
  static moduleName = app-my-component

  static checkSupport() {
    // Run the same checks as the `Component` class
    Component.checkSupport()

    // Check for support of the native clipboard API
    if (!navigator.clipboard) {
      throw new Error(Clipboard API not supported)    
    }
  }
}

Using createAll with your components

Use the createAll function to initialise your components the same way GOV.UK Frontend components are initialised. The function will look up all the HTML elements on which a given component should be initialised and initialise a component for each of them.

import {createAll} from govuk-frontend
import {ProjectComponent, OtherProjectComponent} from ./project-components.js

createAll(ProjectComponent)
// You can provide a config for components that use them
createAll(OtherProjectComponent, {
  anOption: itsValue
})

The createAll function also lets you:

To avoid reimplementing features shared across components, we recommend that your components extend our Component class. However, you can choose not to do this. The only requirements for a component to be used with createAll are:

  • a moduleName static property
  • a constructor expecting an HTML element as a first parameter and any necessary configuration options as a second parameter

Initialise only on part of a page

By default, createAll looks through the whole page for elements to initialise the components on. If you update a page with new markup, like a modal dialog box, you can initialise components on that part of the page only.

import {createAll} from govuk-frontend
import {ProjectComponent} from ./project-component.js

const $element = document.querySelector(.app-dialog)

createAll(ProjectComponent, {SOME_CONFIGURATION}, $element)
// If the component does not need any configuration, pass `null` for the configuration
createAll(ProjectComponent, null, $element)

Handling initialisation errors

By default, createAll catches errors thrown by components during their initialisation and logs them in the console. This makes sure components further down the page still get initialised after an error. You may still want to respond to errors as the components initialise, such as by notifying an error monitoring service, without manually initialising each component.

You can use a function as a third argument to respond to errors being thrown while components are being initialised. If a component throws an error, the function will be called and will receive:

  • the error that was thrown
  • an object with some more context

The context object will contain the following properties:

  • Component: The component class being initialised
  • element: The element the component is being initialised on
  • config: The configuration used for initialisation
import {createAll} from govuk-frontend
import {ProjectComponent} from ./project-component.js

function notifyErrorMonitoringService(error, { Component, element, config }) {
  // Send relevant details to an error monitoring service
}

createAll(ProjectComponent, {SOME_CONFIGURATION}, notifyErrorMonitoringService)
// If you don’t need any configuration, pass `null` for the configuration
createAll(ProjectComponent, null, notifyErrorMonitoringService)

If you need to initialise only on part of the page and respond to errors, you can pass an object in that third argument with the following properties:

  • scope: The element within which to look for components to initialise
  • onError: The callback for responding to errors
import {createAll} from govuk-frontend
import {ProjectComponent} from ./project-component.js

const $element = document.querySelector(.app-dialog)

function notifyErrorMonitoringService(error, { Component, element, config }) {
  // Do something with initialisation error, like sending relevant details to an error monitoring service
}

createAll(ProjectComponent, {SOME_CONFIGURATION}, {
  scope: $element,
  onError: notifyErrorMonitoringService
})
// If you don’t need any configuration, pass `null` for the configuration
createAll(ProjectComponent, null, {
  scope: $element,
  onError: notifyErrorMonitoringService
})

Checking for GOV.UK Frontend support with isSupported()

GOV.UK Frontend components and components that inherit from our Component class will automatically check if GOV.UK Frontend is supported during their initialisation. However, you may want to separately check for support, to avoid unnecessarily running code if GOV.UK Frontend is not supported.

Use the isSupported() function to check whether GOV.UK Frontend is supported in the browser your code is running in.

import {isSupported} from govuk-frontend

if (isSupported()) {
  // Do things if GOV.UK Frontend is supported
}