alphagov / govuk-frontend

GOV.UK Frontend contains the code you need to start building a user interface for government platforms and services.
https://frontend.design-system.service.gov.uk/
MIT License
1.18k stars 325 forks source link

[SPIKE] Navigation - create navigation component #4908

Closed domoscargin closed 7 months ago

domoscargin commented 7 months ago

This PR adds a navigation component to govuk-frontend, wholesale copied from the One Login header, then tweaked slightly.

It's a foundation to investigate https://github.com/alphagov/govuk-design-system/issues/3715

See the thing here: https://govuk-frontend-pr-4908.herokuapp.com/components/navigation

github-actions[bot] commented 7 months ago

:clipboard: Stats

File sizes

File Size
dist/govuk-frontend-development.min.css 119.63 KiB
dist/govuk-frontend-development.min.js 43.8 KiB
packages/govuk-frontend/dist/govuk/all.bundle.js 89.46 KiB
packages/govuk-frontend/dist/govuk/all.bundle.mjs 84.08 KiB
packages/govuk-frontend/dist/govuk/all.mjs 4.27 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend-component.mjs 359 B
packages/govuk-frontend/dist/govuk/govuk-frontend.min.css 119.61 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend.min.js 43.79 KiB
packages/govuk-frontend/dist/govuk/i18n.mjs 5.55 KiB

Modules

File Size (bundled) Size (minified)
all.mjs 79.79 KiB 41.72 KiB
accordion.mjs 22.71 KiB 12.85 KiB
button.mjs 5.98 KiB 2.69 KiB
character-count.mjs 22.4 KiB 9.92 KiB
checkboxes.mjs 5.83 KiB 2.83 KiB
error-summary.mjs 7.89 KiB 3.46 KiB
exit-this-page.mjs 17.1 KiB 9.26 KiB
header.mjs 4.46 KiB 2.6 KiB
navigation.mjs 3.71 KiB 2.39 KiB
notification-banner.mjs 6.26 KiB 2.62 KiB
password-input.mjs 15.15 KiB 7.25 KiB
radios.mjs 4.83 KiB 2.38 KiB
skip-link.mjs 4.39 KiB 2.18 KiB
tabs.mjs 10.13 KiB 6.11 KiB

View stats and visualisations on the review app


Action run for 587369edb62fee2642c9883cef5538e4dc3c95ff

github-actions[bot] commented 7 months ago

Stylesheets changes to npm package

diff --git a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.css b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.css
index 2c37191c5..99c42eded 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.css
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.css
@@ -4098,6 +4098,369 @@ only screen and (min-resolution:2dppx) {
     margin-bottom: 0
 }

+.govuk-navigation__button {
+    display: none;
+    color: #1d70b8;
+    font-family: GDS Transport, arial, sans-serif;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    font-weight: 700;
+    font-size: 1rem;
+    line-height: 1.25;
+    position: relative;
+    align-items: center;
+    min-width: 240px;
+    min-width: -webkit-max-content;
+    min-width: max-content;
+    margin: 0;
+    padding: 10px 0 5px;
+    border: 0;
+    background: none;
+    cursor: pointer
+}
+
+.govuk-navigation__button:focus {
+    outline: 3px solid transparent;
+    color: #0b0c0c;
+    background-color: #fd0;
+    box-shadow: 0 -2px #fd0, 0 4px #0b0c0c;
+    text-decoration: none;
+    -webkit-box-decoration-break: clone;
+    box-decoration-break: clone
+}
+
+.govuk-navigation__button:focus:not(:focus-visible) {
+    outline: none;
+    color: #1d70b8;
+    background: none;
+    box-shadow: none
+}
+
+.govuk-navigation__button:focus-visible {
+    outline: 3px solid transparent;
+    color: #0b0c0c;
+    background-color: #fd0;
+    box-shadow: 0 -2px #fd0, 0 4px #0b0c0c;
+    text-decoration: none;
+    -webkit-box-decoration-break: clone;
+    box-decoration-break: clone
+}
+
+.toggle-enabled .govuk-navigation__button {
+    display: inline;
+    display: flex
+}
+
+@media (min-width:40.0625em) {
+    .toggle-enabled .govuk-navigation__button {
+        display: none
+    }
+}
+
+@media print {
+    .govuk-navigation__button {
+        font-family: sans-serif
+    }
+}
+
+@media (min-width:40.0625em) {
+    .govuk-navigation__button {
+        font-size: 1.1875rem;
+        line-height: 1.3157894737
+    }
+}
+
+@media print {
+    .govuk-navigation__button {
+        font-size: 14pt;
+        line-height: 1.15
+    }
+}
+
+.govuk-navigation__button:after {
+    display: inline-block;
+    width: 0;
+    height: 0;
+    -webkit-clip-path: polygon(0 0, 50% 100%, 100% 0);
+    clip-path: polygon(0 0, 50% 100%, 100% 0);
+    border-color: transparent;
+    border-style: solid;
+    border-width: 8.66px 5px 0;
+    border-top-color: inherit;
+    content: "";
+    margin-left: 5px
+}
+
+.govuk-navigation__button.govuk-navigation__button--open:before {
+    transform: translateY(-15%) rotate(-45deg)
+}
+
+.govuk-navigation__button-icon {
+    margin-left: 10px;
+    font-size: 0
+}
+
+.govuk-navigation__button-icon.govuk-navigation__button-icon--focus,
+.govuk-navigation__button:focus .govuk-navigation__button-icon.govuk-navigation__button-icon--default {
+    display: none
+}
+
+.govuk-navigation__button:focus .govuk-navigation__button-icon.govuk-navigation__button-icon--focus {
+    display: inline
+}
+
+.govuk-navigation__button:focus:not(:focus-visible) .govuk-navigation__button-icon.govuk-navigation__button-icon--default {
+    display: inline
+}
+
+.govuk-navigation__button:focus:not(:focus-visible) .govuk-navigation__button-icon.govuk-navigation__button-icon--focus {
+    display: none
+}
+
+.govuk-navigation__button:focus-visible .govuk-navigation__button-icon.govuk-navigation__button-icon--default {
+    display: none
+}
+
+.govuk-navigation__button:focus-visible .govuk-navigation__button-icon.govuk-navigation__button-icon--focus {
+    display: inline
+}
+
+.govuk-navigation {
+    border-bottom: 1px solid #b1b4b6;
+    background-color: #f3f2f1
+}
+
+@media (max-width:40.0525em) {
+    .govuk-navigation__container {
+        margin-bottom: 5px
+    }
+}
+
+@media (min-width:40.0625em) {
+    .govuk-navigation__container {
+        display: flex;
+        flex-wrap: wrap
+    }
+}
+
+.govuk-navigation__heading {
+    font-family: GDS Transport, arial, sans-serif;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    font-weight: 700;
+    font-size: 1.125rem;
+    line-height: 1.1111111111;
+    margin: 0;
+    padding: 10px 0;
+    color: #0b0c0c;
+    flex-grow: 1
+}
+
+@media print {
+    .govuk-navigation__heading {
+        font-family: sans-serif
+    }
+}
+
+@media (min-width:40.0625em) {
+    .govuk-navigation__heading {
+        font-size: 1.5rem;
+        line-height: 1.25
+    }
+}
+
+@media print {
+    .govuk-navigation__heading {
+        font-size: 18pt;
+        line-height: 1.15
+    }
+}
+
+@media (max-width:40.0525em) {
+    .govuk-navigation__heading {
+        padding: 5px 0
+    }
+}
+
+.govuk-navigation__nav {
+    display: block
+}
+
+.toggle-enabled .govuk-navigation__nav {
+    display: none
+}
+
+.toggle-enabled .govuk-navigation__nav.govuk-navigation__nav--open {
+    display: block
+}
+
+@media (min-width:40.0625em) {
+    .toggle-enabled .govuk-navigation__nav {
+        display: block
+    }
+}
+
+@media (max-width:40.0525em) {
+    .govuk-navigation__nav {
+        width: 100%
+    }
+}
+
+.govuk-navigation__nav-list {
+    font-family: GDS Transport, arial, sans-serif;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    font-weight: 700;
+    font-size: 1rem;
+    line-height: 1.25;
+    margin: 0;
+    padding: 0;
+    list-style: none
+}
+
+@media print {
+    .govuk-navigation__nav-list {
+        font-family: sans-serif
+    }
+}
+
+@media (min-width:40.0625em) {
+    .govuk-navigation__nav-list {
+        font-size: 1.1875rem;
+        line-height: 1.3157894737
+    }
+}
+
+@media print {
+    .govuk-navigation__nav-list {
+        font-size: 14pt;
+        line-height: 1.15
+    }
+}
+
+@media (min-width:40.0625em) {
+    .govuk-navigation__nav-list {
+        font-family: GDS Transport, arial, sans-serif;
+        -webkit-font-smoothing: antialiased;
+        -moz-osx-font-smoothing: grayscale;
+        font-weight: 700;
+        font-size: 1rem;
+        line-height: 1.25
+    }
+}
+
+@media print and (min-width:40.0625em) {
+    .govuk-navigation__nav-list {
+        font-family: sans-serif
+    }
+}
+
+@media (min-width:40.0625em) and (min-width:40.0625em) {
+    .govuk-navigation__nav-list {
+        font-size: 1.1875rem;
+        line-height: 1.3157894737
+    }
+}
+
+@media print and (min-width:40.0625em) {
+    .govuk-navigation__nav-list {
+        font-size: 14pt;
+        line-height: 1.15
+    }
+}
+
+.govuk-navigation__nav-list-item {
+    margin: 15px 0 20px
+}
+
+.govuk-navigation__nav-list-item.govuk-navigation__nav-list-item--active {
+    padding-left: 15px;
+    border-left: 5px solid #1d70b8
+}
+
+@media (min-width:40.0625em) {
+    .govuk-navigation__nav-list-item {
+        display: inline-block;
+        margin: 0 30px 0 0;
+        border-bottom: 5px solid transparent
+    }
+
+    .govuk-navigation__nav-list-item:last-of-type {
+        margin: 0
+    }
+
+    .govuk-navigation__nav-list-item.govuk-navigation__nav-list-item--active {
+        padding-left: 0;
+        border-bottom: 5px solid #1d70b8;
+        border-left: 0
+    }
+}
+
+.govuk-navigation__nav-list-item-link {
+    font-family: GDS Transport, arial, sans-serif;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    text-decoration: underline;
+    text-decoration-thickness: max(1px, .0625rem);
+    text-underline-offset: .1578em
+}
+
+@media print {
+    .govuk-navigation__nav-list-item-link {
+        font-family: sans-serif
+    }
+}
+
+.govuk-navigation__nav-list-item-link:hover {
+    text-decoration-thickness: max(3px, .1875rem, .12em);
+    -webkit-text-decoration-skip-ink: none;
+    text-decoration-skip-ink: none;
+    -webkit-text-decoration-skip: none;
+    text-decoration-skip: none
+}
+
+.govuk-navigation__nav-list-item-link:focus {
+    outline: 3px solid transparent;
+    background-color: #fd0;
+    box-shadow: 0 -2px #fd0, 0 4px #0b0c0c;
+    text-decoration: none;
+    -webkit-box-decoration-break: clone;
+    box-decoration-break: clone
+}
+
+.govuk-navigation__nav-list-item-link:visited {
+    color: #4c2c92
+}
+
+.govuk-navigation__nav-list-item-link:link,
+.govuk-navigation__nav-list-item-link:visited {
+    color: #1d70b8
+}
+
+.govuk-navigation__nav-list-item-link:hover {
+    color: #003078
+}
+
+.govuk-navigation__nav-list-item-link:active,
+.govuk-navigation__nav-list-item-link:focus {
+    color: #0b0c0c
+}
+
+.govuk-navigation__nav-list-item-link:not(:hover) {
+    text-decoration: none
+}
+
+@media (min-width:40.0625em) {
+    .govuk-navigation__nav-list-item-link {
+        display: inline-block;
+        padding: 10px 0
+    }
+
+    .govuk-navigation__nav-list-item-link:focus {
+        box-shadow: 0 -5px #fd0, 0 5px #0b0c0c
+    }
+}
+
 .govuk-notification-banner {
     font-family: GDS Transport, arial, sans-serif;
     -webkit-font-smoothing: antialiased;

Action run for 13efac5faf81336f16df5a924f2f4e3c754a08f1

github-actions[bot] commented 7 months ago

Rendered HTML changes to npm package

diff --git a/packages/govuk-frontend/dist/govuk/components/navigation/template-default.html b/packages/govuk-frontend/dist/govuk/components/navigation/template-default.html
new file mode 100644
index 000000000..6a3582a76
--- /dev/null
+++ b/packages/govuk-frontend/dist/govuk/components/navigation/template-default.html
@@ -0,0 +1,61 @@
+<div class="govuk-navigation" data-module="govuk-navigation">
+  <div class="govuk-width-container">
+    <div class="govuk-navigation__container">
+      <div>
+        <!-- Label the toggle button appropriately (you might want to use the name of your service) to provide support for screen reader users -->
+        <button type="button"
+          aria-controls="govuk-navigation__nav"
+          data-open-class="govuk-navigation__button--open"
+          aria-label="Show service navigation menu"
+          data-label-for-show="Show service navigation menu"
+          data-label-for-hide="Hide service navigation menu"
+          data-text-for-show="Menu"
+          data-text-for-hide="Close"
+          aria-expanded="false"
+          class="govuk-navigation__button js-x-header-toggle">
+          <span class="govuk-navigation__button-content">Menu</span>
+        </button>
+        <!-- Important! Label your navigation appropriately! When there are multiple navigation landmarks on a page, additional labelling is required to provide support for screen reader users -->
+        <nav aria-label=" menu">
+          <ul class="govuk-navigation__nav-list govuk-navigation__nav" id="govuk-navigation__nav" data-open-class="govuk-navigation__nav--open">            <!-- Use the "govuk-navigation__nav-list-item--active" modifier class on the li to visually highlight the page the user is currently viewing -->
+            <li class="govuk-navigation__nav-list-item govuk-navigation__nav-list-item--active">
+              <!-- Use the aria-current="page" attribute on the anchor tag -->
+              <a class="govuk-navigation__nav-list-item-link"aria-current="page"                href="#get-started">
+                Get started
+              </a>
+            </li>            <!-- Use the "govuk-navigation__nav-list-item--active" modifier class on the li to visually highlight the page the user is currently viewing -->
+            <li class="govuk-navigation__nav-list-item ">
+              <!-- Use the aria-current="page" attribute on the anchor tag -->
+              <a class="govuk-navigation__nav-list-item-link"                href="#styles">
+                Styles
+              </a>
+            </li>            <!-- Use the "govuk-navigation__nav-list-item--active" modifier class on the li to visually highlight the page the user is currently viewing -->
+            <li class="govuk-navigation__nav-list-item ">
+              <!-- Use the aria-current="page" attribute on the anchor tag -->
+              <a class="govuk-navigation__nav-list-item-link"                href="#components">
+                Components
+              </a>
+            </li>            <!-- Use the "govuk-navigation__nav-list-item--active" modifier class on the li to visually highlight the page the user is currently viewing -->
+            <li class="govuk-navigation__nav-list-item ">
+              <!-- Use the aria-current="page" attribute on the anchor tag -->
+              <a class="govuk-navigation__nav-list-item-link"                href="#patterns">
+                Patterns
+              </a>
+            </li>            <!-- Use the "govuk-navigation__nav-list-item--active" modifier class on the li to visually highlight the page the user is currently viewing -->
+            <li class="govuk-navigation__nav-list-item ">
+              <!-- Use the aria-current="page" attribute on the anchor tag -->
+              <a class="govuk-navigation__nav-list-item-link"                href="#community">
+                Community
+              </a>
+            </li>            <!-- Use the "govuk-navigation__nav-list-item--active" modifier class on the li to visually highlight the page the user is currently viewing -->
+            <li class="govuk-navigation__nav-list-item ">
+              <!-- Use the aria-current="page" attribute on the anchor tag -->
+              <a class="govuk-navigation__nav-list-item-link"                href="#accessibility">
+                Accessibility
+              </a>
+            </li>          </ul>
+        </nav>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/packages/govuk-frontend/dist/govuk/components/navigation/template-with-service-name-and-links.html b/packages/govuk-frontend/dist/govuk/components/navigation/template-with-service-name-and-links.html
new file mode 100644
index 000000000..6c2a8d0d2
--- /dev/null
+++ b/packages/govuk-frontend/dist/govuk/components/navigation/template-with-service-name-and-links.html
@@ -0,0 +1,62 @@
+<div class="govuk-navigation" data-module="govuk-navigation">
+  <div class="govuk-width-container">
+    <div class="govuk-navigation__container">
+        <h2 class="govuk-navigation__heading">Service Name</h2>
+      <div>
+        <!-- Label the toggle button appropriately (you might want to use the name of your service) to provide support for screen reader users -->
+        <button type="button"
+          aria-controls="govuk-navigation__nav"
+          data-open-class="govuk-navigation__button--open"
+          aria-label="Show service navigation menu"
+          data-label-for-show="Show service navigation menu"
+          data-label-for-hide="Hide service navigation menu"
+          data-text-for-show="Menu"
+          data-text-for-hide="Close"
+          aria-expanded="false"
+          class="govuk-navigation__button js-x-header-toggle">
+          <span class="govuk-navigation__button-content">Menu</span>
+        </button>
+        <!-- Important! Label your navigation appropriately! When there are multiple navigation landmarks on a page, additional labelling is required to provide support for screen reader users -->
+        <nav aria-label="Service Name menu">
+          <ul class="govuk-navigation__nav-list govuk-navigation__nav" id="govuk-navigation__nav" data-open-class="govuk-navigation__nav--open">            <!-- Use the "govuk-navigation__nav-list-item--active" modifier class on the li to visually highlight the page the user is currently viewing -->
+            <li class="govuk-navigation__nav-list-item govuk-navigation__nav-list-item--active">
+              <!-- Use the aria-current="page" attribute on the anchor tag -->
+              <a class="govuk-navigation__nav-list-item-link"aria-current="page"                href="#get-started">
+                Get started
+              </a>
+            </li>            <!-- Use the "govuk-navigation__nav-list-item--active" modifier class on the li to visually highlight the page the user is currently viewing -->
+            <li class="govuk-navigation__nav-list-item ">
+              <!-- Use the aria-current="page" attribute on the anchor tag -->
+              <a class="govuk-navigation__nav-list-item-link"                href="#styles">
+                Styles
+              </a>
+            </li>            <!-- Use the "govuk-navigation__nav-list-item--active" modifier class on the li to visually highlight the page the user is currently viewing -->
+            <li class="govuk-navigation__nav-list-item ">
+              <!-- Use the aria-current="page" attribute on the anchor tag -->
+              <a class="govuk-navigation__nav-list-item-link"                href="#components">
+                Components
+              </a>
+            </li>            <!-- Use the "govuk-navigation__nav-list-item--active" modifier class on the li to visually highlight the page the user is currently viewing -->
+            <li class="govuk-navigation__nav-list-item ">
+              <!-- Use the aria-current="page" attribute on the anchor tag -->
+              <a class="govuk-navigation__nav-list-item-link"                href="#patterns">
+                Patterns
+              </a>
+            </li>            <!-- Use the "govuk-navigation__nav-list-item--active" modifier class on the li to visually highlight the page the user is currently viewing -->
+            <li class="govuk-navigation__nav-list-item ">
+              <!-- Use the aria-current="page" attribute on the anchor tag -->
+              <a class="govuk-navigation__nav-list-item-link"                href="#community">
+                Community
+              </a>
+            </li>            <!-- Use the "govuk-navigation__nav-list-item--active" modifier class on the li to visually highlight the page the user is currently viewing -->
+            <li class="govuk-navigation__nav-list-item ">
+              <!-- Use the aria-current="page" attribute on the anchor tag -->
+              <a class="govuk-navigation__nav-list-item-link"                href="#accessibility">
+                Accessibility
+              </a>
+            </li>          </ul>
+        </nav>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/packages/govuk-frontend/dist/govuk/components/navigation/template-with-service-name-only.html b/packages/govuk-frontend/dist/govuk/components/navigation/template-with-service-name-only.html
new file mode 100644
index 000000000..dedc79217
--- /dev/null
+++ b/packages/govuk-frontend/dist/govuk/components/navigation/template-with-service-name-only.html
@@ -0,0 +1,7 @@
+<div class="govuk-navigation" data-module="govuk-navigation">
+  <div class="govuk-width-container">
+    <div class="govuk-navigation__container">
+        <h2 class="govuk-navigation__heading">Service Name</h2>
+    </div>
+  </div>
+</div>

Action run for 13efac5faf81336f16df5a924f2f4e3c754a08f1

github-actions[bot] commented 7 months ago

Other changes to npm package

diff --git a/packages/govuk-frontend/dist/govuk/all.bundle.js b/packages/govuk-frontend/dist/govuk/all.bundle.js
index 66ef07672..327f73b04 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.js
@@ -1704,6 +1704,53 @@
   }
   Header.moduleName = 'govuk-header';

+  class Navigation extends GOVUKFrontendComponent {
+    /**
+     * Class constructor
+     *
+     * @param {Element | null} $module - HTML element to use for header
+     */
+    constructor($module) {
+      super();
+      if (!$module) {
+        throw new ElementError({
+          componentName: 'Header',
+          element: $module,
+          identifier: 'Root element (`$module`)'
+        });
+      }
+      const $nav = $module;
+      $nav.$menuButton = $nav.querySelector('.js-x-header-toggle');
+      $nav.$menu = $nav.$menuButton && $nav.querySelector('#' + $nav.$menuButton.getAttribute('aria-controls'));
+      $nav.menuItems = $nav.$menu && $nav.$menu.querySelectorAll('li');
+      if (!$nav.$menuButton || !$nav.$menu || $nav.menuItems.length < 2) {
+        return;
+      }
+      $nav.classList.add('toggle-enabled');
+      $nav.$menuOpenClass = $nav.$menu && $nav.$menu.dataset.openClass;
+      $nav.$menuButtonOpenClass = $nav.$menuButton && $nav.$menuButton.dataset.openClass;
+      $nav.$menuButtonOpenLabel = $nav.$menuButton && $nav.$menuButton.dataset.labelForShow;
+      $nav.$menuButtonCloseLabel = $nav.$menuButton && $nav.$menuButton.dataset.labelForHide;
+      $nav.$menuButtonOpenText = $nav.$menuButton && $nav.$menuButton.dataset.textForShow;
+      $nav.$menuButtonCloseText = $nav.$menuButton && $nav.$menuButton.dataset.textForHide;
+      $nav.isOpen = false;
+      $nav.$menuButton.addEventListener('click', this.handleMenuButtonClick.bind($nav));
+    }
+    handleMenuButtonClick() {
+      this.isOpen = !this.isOpen;
+      this.$menuOpenClass && this.$menu.classList.toggle(this.$menuOpenClass, this.isOpen);
+      this.$menuButtonOpenClass && this.$menuButton.classList.toggle(this.$menuButtonOpenClass, this.isOpen);
+      this.$menuButton.setAttribute('aria-expanded', this.isOpen);
+      if (this.$menuButtonCloseLabel && this.$menuButtonOpenLabel) {
+        this.$menuButton.setAttribute('aria-label', this.isOpen ? this.$menuButtonCloseLabel : this.$menuButtonOpenLabel);
+      }
+      if (this.$menuButtonCloseText && this.$menuButtonOpenText) {
+        this.$menuButton.innerHTML = this.isOpen ? this.$menuButtonCloseText : this.$menuButtonOpenText;
+      }
+    }
+  }
+  Navigation.moduleName = 'govuk-navigation';
+
   /**
    * Notification Banner component
    *
@@ -2369,7 +2416,7 @@
       console.log(new SupportError());
       return;
     }
-    const components = [[Accordion, config.accordion], [Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [ExitThisPage, config.exitThisPage], [Header], [NotificationBanner, config.notificationBanner], [PasswordInput, config.passwordInput], [Radios], [SkipLink], [Tabs]];
+    const components = [[Accordion, config.accordion], [Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [ExitThisPage, config.exitThisPage], [Header], [Navigation], [NotificationBanner, config.notificationBanner], [PasswordInput, config.passwordInput], [Radios], [SkipLink], [Tabs]];
     const $scope = (_config$scope = config.scope) != null ? _config$scope : document;
     components.forEach(([Component, config]) => {
       const $elements = $scope.querySelectorAll(`[data-module="${Component.moduleName}"]`);
@@ -2424,6 +2471,7 @@
   exports.ErrorSummary = ErrorSummary;
   exports.ExitThisPage = ExitThisPage;
   exports.Header = Header;
+  exports.Navigation = Navigation;
   exports.NotificationBanner = NotificationBanner;
   exports.PasswordInput = PasswordInput;
   exports.Radios = Radios;
diff --git a/packages/govuk-frontend/dist/govuk/all.bundle.mjs b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
index 669d06852..2855c3c54 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
@@ -1698,6 +1698,53 @@ class Header extends GOVUKFrontendComponent {
 }
 Header.moduleName = 'govuk-header';

+class Navigation extends GOVUKFrontendComponent {
+  /**
+   * Class constructor
+   *
+   * @param {Element | null} $module - HTML element to use for header
+   */
+  constructor($module) {
+    super();
+    if (!$module) {
+      throw new ElementError({
+        componentName: 'Header',
+        element: $module,
+        identifier: 'Root element (`$module`)'
+      });
+    }
+    const $nav = $module;
+    $nav.$menuButton = $nav.querySelector('.js-x-header-toggle');
+    $nav.$menu = $nav.$menuButton && $nav.querySelector('#' + $nav.$menuButton.getAttribute('aria-controls'));
+    $nav.menuItems = $nav.$menu && $nav.$menu.querySelectorAll('li');
+    if (!$nav.$menuButton || !$nav.$menu || $nav.menuItems.length < 2) {
+      return;
+    }
+    $nav.classList.add('toggle-enabled');
+    $nav.$menuOpenClass = $nav.$menu && $nav.$menu.dataset.openClass;
+    $nav.$menuButtonOpenClass = $nav.$menuButton && $nav.$menuButton.dataset.openClass;
+    $nav.$menuButtonOpenLabel = $nav.$menuButton && $nav.$menuButton.dataset.labelForShow;
+    $nav.$menuButtonCloseLabel = $nav.$menuButton && $nav.$menuButton.dataset.labelForHide;
+    $nav.$menuButtonOpenText = $nav.$menuButton && $nav.$menuButton.dataset.textForShow;
+    $nav.$menuButtonCloseText = $nav.$menuButton && $nav.$menuButton.dataset.textForHide;
+    $nav.isOpen = false;
+    $nav.$menuButton.addEventListener('click', this.handleMenuButtonClick.bind($nav));
+  }
+  handleMenuButtonClick() {
+    this.isOpen = !this.isOpen;
+    this.$menuOpenClass && this.$menu.classList.toggle(this.$menuOpenClass, this.isOpen);
+    this.$menuButtonOpenClass && this.$menuButton.classList.toggle(this.$menuButtonOpenClass, this.isOpen);
+    this.$menuButton.setAttribute('aria-expanded', this.isOpen);
+    if (this.$menuButtonCloseLabel && this.$menuButtonOpenLabel) {
+      this.$menuButton.setAttribute('aria-label', this.isOpen ? this.$menuButtonCloseLabel : this.$menuButtonOpenLabel);
+    }
+    if (this.$menuButtonCloseText && this.$menuButtonOpenText) {
+      this.$menuButton.innerHTML = this.isOpen ? this.$menuButtonCloseText : this.$menuButtonOpenText;
+    }
+  }
+}
+Navigation.moduleName = 'govuk-navigation';
+
 /**
  * Notification Banner component
  *
@@ -2363,7 +2410,7 @@ function initAll(config) {
     console.log(new SupportError());
     return;
   }
-  const components = [[Accordion, config.accordion], [Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [ExitThisPage, config.exitThisPage], [Header], [NotificationBanner, config.notificationBanner], [PasswordInput, config.passwordInput], [Radios], [SkipLink], [Tabs]];
+  const components = [[Accordion, config.accordion], [Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [ExitThisPage, config.exitThisPage], [Header], [Navigation], [NotificationBanner, config.notificationBanner], [PasswordInput, config.passwordInput], [Radios], [SkipLink], [Tabs]];
   const $scope = (_config$scope = config.scope) != null ? _config$scope : document;
   components.forEach(([Component, config]) => {
     const $elements = $scope.querySelectorAll(`[data-module="${Component.moduleName}"]`);
@@ -2411,5 +2458,5 @@ function initAll(config) {
  * @typedef {keyof Config} ConfigKey
  */

-export { Accordion, Button, CharacterCount, Checkboxes, ErrorSummary, ExitThisPage, Header, NotificationBanner, PasswordInput, Radios, SkipLink, Tabs, initAll, version };
+export { Accordion, Button, CharacterCount, Checkboxes, ErrorSummary, ExitThisPage, Header, Navigation, NotificationBanner, PasswordInput, Radios, SkipLink, Tabs, initAll, version };
 //# sourceMappingURL=all.bundle.mjs.map
diff --git a/packages/govuk-frontend/dist/govuk/all.mjs b/packages/govuk-frontend/dist/govuk/all.mjs
index 02cb75e14..58bc6ec0c 100644
--- a/packages/govuk-frontend/dist/govuk/all.mjs
+++ b/packages/govuk-frontend/dist/govuk/all.mjs
@@ -7,6 +7,7 @@ import { Checkboxes } from './components/checkboxes/checkboxes.mjs';
 import { ErrorSummary } from './components/error-summary/error-summary.mjs';
 import { ExitThisPage } from './components/exit-this-page/exit-this-page.mjs';
 import { Header } from './components/header/header.mjs';
+import { Navigation } from './components/navigation/navigation.mjs';
 import { NotificationBanner } from './components/notification-banner/notification-banner.mjs';
 import { PasswordInput } from './components/password-input/password-input.mjs';
 import { Radios } from './components/radios/radios.mjs';
@@ -29,7 +30,7 @@ function initAll(config) {
     console.log(new SupportError());
     return;
   }
-  const components = [[Accordion, config.accordion], [Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [ExitThisPage, config.exitThisPage], [Header], [NotificationBanner, config.notificationBanner], [PasswordInput, config.passwordInput], [Radios], [SkipLink], [Tabs]];
+  const components = [[Accordion, config.accordion], [Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [ExitThisPage, config.exitThisPage], [Header], [Navigation], [NotificationBanner, config.notificationBanner], [PasswordInput, config.passwordInput], [Radios], [SkipLink], [Tabs]];
   const $scope = (_config$scope = config.scope) != null ? _config$scope : document;
   components.forEach(([Component, config]) => {
     const $elements = $scope.querySelectorAll(`[data-module="${Component.moduleName}"]`);
@@ -77,5 +78,5 @@ function initAll(config) {
  * @typedef {keyof Config} ConfigKey
  */

-export { Accordion, Button, CharacterCount, Checkboxes, ErrorSummary, ExitThisPage, Header, NotificationBanner, PasswordInput, Radios, SkipLink, Tabs, initAll };
+export { Accordion, Button, CharacterCount, Checkboxes, ErrorSummary, ExitThisPage, Header, Navigation, NotificationBanner, PasswordInput, Radios, SkipLink, Tabs, initAll };
 //# sourceMappingURL=all.mjs.map
diff --git a/packages/govuk-frontend/dist/govuk/components/_all.scss b/packages/govuk-frontend/dist/govuk/components/_all.scss
index e0f702951..536865602 100644
--- a/packages/govuk-frontend/dist/govuk/components/_all.scss
+++ b/packages/govuk-frontend/dist/govuk/components/_all.scss
@@ -20,6 +20,7 @@
 @import "input/index";
 @import "inset-text/index";
 @import "label/index";
+@import "navigation/index";
 @import "notification-banner/index";
 @import "pagination/index";
 @import "panel/index";
diff --git a/packages/govuk-frontend/dist/govuk/components/navigation/README.md b/packages/govuk-frontend/dist/govuk/components/navigation/README.md
new file mode 100644
index 000000000..82896d302
--- /dev/null
+++ b/packages/govuk-frontend/dist/govuk/components/navigation/README.md
@@ -0,0 +1,5 @@
+## Spike - primary navigation component using Design System website code
+
+- Navigation template: https://github.com/alphagov/govuk-design-system/blob/main/views/partials/_navigation.njk
+- Navigation stylesheet: https://github.com/alphagov/govuk-design-system/blob/main/src/stylesheets/components/_navigation.scss
+- Navigation JavaScript: https://github.com/alphagov/govuk-design-system/blob/main/src/javascripts/components/navigation.mjs
diff --git a/packages/govuk-frontend/dist/govuk/components/navigation/_index.scss b/packages/govuk-frontend/dist/govuk/components/navigation/_index.scss
new file mode 100644
index 000000000..ff920bbca
--- /dev/null
+++ b/packages/govuk-frontend/dist/govuk/components/navigation/_index.scss
@@ -0,0 +1,216 @@
+// start mixins and variables
+$govuk-header-link-underline-thickness: 3px;
+
+@mixin toggle-button-focus($default-text-colour) {
+  color: $default-text-colour;
+  // apply focus style on :focus for browsers which support :focus but not :focus-visible
+  &:focus {
+    @include govuk-focused-text;
+
+    // overwrite previous styles for browsers which support :focus-visible
+    &:not(:focus-visible) {
+      outline: none;
+      color: $default-text-colour;
+      background: none;
+      box-shadow: none;
+    }
+
+    // apply focus style on :focus-visible for browsers which support :focus-visible
+    &-visible {
+      @include govuk-focused-text;
+    }
+  }
+}
+
+@mixin nav-style($nav-open-class) {
+  display: block;
+  // if JS is unavailable, the nav links are expanded and the toggle button is hidden
+  .toggle-enabled & {
+    display: none;
+
+    &#{$nav-open-class} {
+      display: block;
+    }
+
+    @include govuk-media-query($from: tablet) {
+      display: block;
+    }
+  }
+
+  @include govuk-media-query($until: tablet) {
+    width: 100%;
+  }
+}
+
+.govuk-navigation__button {
+  display: none;
+
+  @include toggle-button-focus($govuk-link-colour);
+
+  .toggle-enabled & {
+    display: inline;
+    display: flex;
+
+    @include govuk-media-query($from: tablet) {
+      display: none;
+    }
+  }
+
+  @include govuk-font($size: 19, $weight: bold);
+  position: relative;
+  align-items: center;
+  min-width: 240px;
+  min-width: -webkit-max-content;
+  min-width: max-content;
+  margin: 0;
+  padding: govuk-spacing(2) 0 govuk-spacing(1) 0;
+  border: 0;
+  background: none;
+  cursor: pointer;
+
+  &::after {
+    @include govuk-shape-arrow($direction: down, $base: 10px, $display: inline-block);
+    content: "";
+    margin-left: govuk-spacing(1);
+  }
+
+  &.govuk-navigation__button--open {
+    &::before {
+      transform: translateY(-15%) rotate(-45deg);
+    }
+  }
+}
+
+.govuk-navigation__button-icon {
+  margin-left: govuk-spacing(2);
+  font-size: 0;
+
+  &.govuk-navigation__button-icon--focus {
+    display: none;
+  }
+
+  // apply focus style on :focus for browsers which support :focus but not :focus-visible
+  .govuk-navigation__button:focus & {
+    &.govuk-navigation__button-icon--default {
+      display: none;
+    }
+
+    &.govuk-navigation__button-icon--focus {
+      display: inline;
+    }
+  }
+
+  // overwrite previous styles for browsers which support :focus-visible
+  .govuk-navigation__button:focus:not(:focus-visible) & {
+    &.govuk-navigation__button-icon--default {
+      display: inline;
+    }
+
+    &.govuk-navigation__button-icon--focus {
+      display: none;
+    }
+  }
+
+  // apply focus style on :focus-visible for browsers which support :focus-visible
+  .govuk-navigation__button:focus-visible & {
+    &.govuk-navigation__button-icon--default {
+      display: none;
+    }
+
+    &.govuk-navigation__button-icon--focus {
+      display: inline;
+    }
+  }
+}
+
+// start service navigation styles
+.govuk-navigation {
+  border-bottom: 1px solid govuk-colour("mid-grey");
+  background-color: govuk-colour("light-grey");
+}
+
+.govuk-navigation__container {
+  @include govuk-media-query($until: tablet) {
+    margin-bottom: govuk-spacing(1);
+  }
+
+  @include govuk-media-query($from: tablet) {
+    display: flex;
+    flex-wrap: wrap;
+  }
+}
+
+.govuk-navigation__heading {
+  @include govuk-font($size: 24, $weight: bold);
+  margin: 0;
+  padding: govuk-spacing(2) 0;
+  color: $govuk-text-colour;
+  flex-grow: 1;
+
+  @include govuk-media-query($until: tablet) {
+    padding: govuk-spacing(1) 0;
+  }
+}
+
+.govuk-navigation__nav {
+  @include nav-style(".govuk-navigation__nav--open");
+}
+
+.govuk-navigation__nav-list {
+  @include govuk-font($size: 19, $weight: bold);
+  margin: 0;
+  padding: 0;
+  list-style: none;
+
+  @include govuk-media-query($from: tablet) {
+    @include govuk-font($size: 19, $weight: bold);
+  }
+}
+
+.govuk-navigation__nav-list-item {
+  margin: govuk-spacing(3) 0 govuk-spacing(4);
+
+  &.govuk-navigation__nav-list-item--active {
+    padding-left: govuk-spacing(3);
+    border-left: govuk-spacing(1) solid $govuk-link-colour;
+  }
+
+  @include govuk-media-query($from: tablet) {
+    display: inline-block;
+    margin: 0 govuk-spacing(6) 0 0;
+    border-bottom: govuk-spacing(1) solid transparent;
+
+    &:last-of-type {
+      margin: 0;
+    }
+
+    &.govuk-navigation__nav-list-item--active {
+      padding-left: 0;
+      border-bottom: govuk-spacing(1) solid $govuk-link-colour;
+      border-left: 0;
+    }
+  }
+}
+
+.govuk-navigation__nav-list-item-link {
+  @include govuk-link-common;
+  @include govuk-link-style-default;
+  @include govuk-link-style-no-visited-state;
+
+  &:not(:hover) {
+    text-decoration: none;
+  }
+
+  @include govuk-media-query($from: tablet) {
+    display: inline-block;
+    padding: govuk-spacing(2) 0 govuk-spacing(2);
+
+    &:focus {
+      box-shadow:
+        0 (-(govuk-spacing(1))) $govuk-focus-colour,
+        0 govuk-spacing(1) $govuk-focus-text-colour;
+    }
+  }
+}
+
+/*# sourceMappingURL=_index.scss.map */
diff --git a/packages/govuk-frontend/dist/govuk/components/navigation/_navigation.scss b/packages/govuk-frontend/dist/govuk/components/navigation/_navigation.scss
new file mode 100644
index 000000000..df43827e4
--- /dev/null
+++ b/packages/govuk-frontend/dist/govuk/components/navigation/_navigation.scss
@@ -0,0 +1,6 @@
+@import "../../base";
+@import "../../objects/width-container";
+@import "../../objects/grid";
+@import "./index";
+
+/*# sourceMappingURL=_navigation.scss.map */
diff --git a/packages/govuk-frontend/dist/govuk/components/navigation/fixtures.json b/packages/govuk-frontend/dist/govuk/components/navigation/fixtures.json
new file mode 100644
index 000000000..ccabd900e
--- /dev/null
+++ b/packages/govuk-frontend/dist/govuk/components/navigation/fixtures.json
@@ -0,0 +1,91 @@
+{
+    "component": "navigation",
+    "fixtures": [
+        {
+            "name": "default",
+            "options": {
+                "name": "example-default",
+                "items": [
+                    {
+                        "label": "Get started",
+                        "url": "#get-started",
+                        "active": true
+                    },
+                    {
+                        "label": "Styles",
+                        "url": "#styles"
+                    },
+                    {
+                        "label": "Components",
+                        "url": "#components"
+                    },
+                    {
+                        "label": "Patterns",
+                        "url": "#patterns"
+                    },
+                    {
+                        "label": "Community",
+                        "url": "#community"
+                    },
+                    {
+                        "label": "Accessibility",
+                        "url": "#accessibility"
+                    }
+                ]
+            },
+            "hidden": false,
+            "description": "",
+            "previewLayoutModifiers": [],
+            "html": "<div class=\"govuk-navigation\" data-module=\"govuk-navigation\">\n  <div class=\"govuk-width-container\">\n    <div class=\"govuk-navigation__container\">\n      <div>\n        <!-- Label the toggle button appropriately (you might want to use the name of your service) to provide support for screen reader users -->\n        <button type=\"button\"\n          aria-controls=\"govuk-navigation__nav\"\n          data-open-class=\"govuk-navigation__button--open\"\n          aria-label=\"Show service navigation menu\"\n          data-label-for-show=\"Show service navigation menu\"\n          data-label-for-hide=\"Hide service navigation menu\"\n          data-text-for-show=\"Menu\"\n          data-text-for-hide=\"Close\"\n          aria-expanded=\"false\"\n          class=\"govuk-navigation__button js-x-header-toggle\">\n          <span class=\"govuk-navigation__button-content\">Menu</span>\n        </button>\n        <!-- Important! Label your navigation appropriately! When there are multiple navigation landmarks on a page, additional labelling is required to provide support for screen reader users -->\n        <nav aria-label=\" menu\">\n          <ul class=\"govuk-navigation__nav-list govuk-navigation__nav\" id=\"govuk-navigation__nav\" data-open-class=\"govuk-navigation__nav--open\">            <!-- Use the \"govuk-navigation__nav-list-item--active\" modifier class on the li to visually highlight the page the user is currently viewing -->\n            <li class=\"govuk-navigation__nav-list-item govuk-navigation__nav-list-item--active\">\n              <!-- Use the aria-current=\"page\" attribute on the anchor tag -->\n              <a class=\"govuk-navigation__nav-list-item-link\"aria-current=\"page\"                href=\"#get-started\">\n                Get started\n              </a>\n            </li>            <!-- Use the \"govuk-navigation__nav-list-item--active\" modifier class on the li to visually highlight the page the user is currently viewing -->\n            <li class=\"govuk-navigation__nav-list-item \">\n              <!-- Use the aria-current=\"page\" attribute on the anchor tag -->\n              <a class=\"govuk-navigation__nav-list-item-link\"                href=\"#styles\">\n                Styles\n              </a>\n            </li>            <!-- Use the \"govuk-navigation__nav-list-item--active\" modifier class on the li to visually highlight the page the user is currently viewing -->\n            <li class=\"govuk-navigation__nav-list-item \">\n              <!-- Use the aria-current=\"page\" attribute on the anchor tag -->\n              <a class=\"govuk-navigation__nav-list-item-link\"                href=\"#components\">\n                Components\n              </a>\n            </li>            <!-- Use the \"govuk-navigation__nav-list-item--active\" modifier class on the li to visually highlight the page the user is currently viewing -->\n            <li class=\"govuk-navigation__nav-list-item \">\n              <!-- Use the aria-current=\"page\" attribute on the anchor tag -->\n              <a class=\"govuk-navigation__nav-list-item-link\"                href=\"#patterns\">\n                Patterns\n              </a>\n            </li>            <!-- Use the \"govuk-navigation__nav-list-item--active\" modifier class on the li to visually highlight the page the user is currently viewing -->\n            <li class=\"govuk-navigation__nav-list-item \">\n              <!-- Use the aria-current=\"page\" attribute on the anchor tag -->\n              <a class=\"govuk-navigation__nav-list-item-link\"                href=\"#community\">\n                Community\n              </a>\n            </li>            <!-- Use the \"govuk-navigation__nav-list-item--active\" modifier class on the li to visually highlight the page the user is currently viewing -->\n            <li class=\"govuk-navigation__nav-list-item \">\n              <!-- Use the aria-current=\"page\" attribute on the anchor tag -->\n              <a class=\"govuk-navigation__nav-list-item-link\"                href=\"#accessibility\">\n                Accessibility\n              </a>\n            </li>          </ul>\n        </nav>\n      </div>\n    </div>\n  </div>\n</div>"
+        },
+        {
+            "name": "with service name only",
+            "options": {
+                "name": "example-service-name",
+                "serviceName": "Service Name"
+            },
+            "hidden": false,
+            "description": "",
+            "previewLayoutModifiers": [],
+            "html": "<div class=\"govuk-navigation\" data-module=\"govuk-navigation\">\n  <div class=\"govuk-width-container\">\n    <div class=\"govuk-navigation__container\">\n        <h2 class=\"govuk-navigation__heading\">Service Name</h2>\n    </div>\n  </div>\n</div>"
+        },
+        {
+            "name": "with service name and links",
+            "options": {
+                "name": "example-name-and-links",
+                "serviceName": "Service Name",
+                "items": [
+                    {
+                        "label": "Get started",
+                        "url": "#get-started",
+                        "active": true
+                    },
+                    {
+                        "label": "Styles",
+                        "url": "#styles"
+                    },
+                    {
+                        "label": "Components",
+                        "url": "#components"
+                    },
+                    {
+                        "label": "Patterns",
+                        "url": "#patterns"
+                    },
+                    {
+                        "label": "Community",
+                        "url": "#community"
+                    },
+                    {
+                        "label": "Accessibility",
+                        "url": "#accessibility"
+                    }
+                ]
+            },
+            "hidden": false,
+            "description": "",
+            "previewLayoutModifiers": [],
+            "html": "<div class=\"govuk-navigation\" data-module=\"govuk-navigation\">\n  <div class=\"govuk-width-container\">\n    <div class=\"govuk-navigation__container\">\n        <h2 class=\"govuk-navigation__heading\">Service Name</h2>\n      <div>\n        <!-- Label the toggle button appropriately (you might want to use the name of your service) to provide support for screen reader users -->\n        <button type=\"button\"\n          aria-controls=\"govuk-navigation__nav\"\n          data-open-class=\"govuk-navigation__button--open\"\n          aria-label=\"Show service navigation menu\"\n          data-label-for-show=\"Show service navigation menu\"\n          data-label-for-hide=\"Hide service navigation menu\"\n          data-text-for-show=\"Menu\"\n          data-text-for-hide=\"Close\"\n          aria-expanded=\"false\"\n          class=\"govuk-navigation__button js-x-header-toggle\">\n          <span class=\"govuk-navigation__button-content\">Menu</span>\n        </button>\n        <!-- Important! Label your navigation appropriately! When there are multiple navigation landmarks on a page, additional labelling is required to provide support for screen reader users -->\n        <nav aria-label=\"Service Name menu\">\n          <ul class=\"govuk-navigation__nav-list govuk-navigation__nav\" id=\"govuk-navigation__nav\" data-open-class=\"govuk-navigation__nav--open\">            <!-- Use the \"govuk-navigation__nav-list-item--active\" modifier class on the li to visually highlight the page the user is currently viewing -->\n            <li class=\"govuk-navigation__nav-list-item govuk-navigation__nav-list-item--active\">\n              <!-- Use the aria-current=\"page\" attribute on the anchor tag -->\n              <a class=\"govuk-navigation__nav-list-item-link\"aria-current=\"page\"                href=\"#get-started\">\n                Get started\n              </a>\n            </li>            <!-- Use the \"govuk-navigation__nav-list-item--active\" modifier class on the li to visually highlight the page the user is currently viewing -->\n            <li class=\"govuk-navigation__nav-list-item \">\n              <!-- Use the aria-current=\"page\" attribute on the anchor tag -->\n              <a class=\"govuk-navigation__nav-list-item-link\"                href=\"#styles\">\n                Styles\n              </a>\n            </li>            <!-- Use the \"govuk-navigation__nav-list-item--active\" modifier class on the li to visually highlight the page the user is currently viewing -->\n            <li class=\"govuk-navigation__nav-list-item \">\n              <!-- Use the aria-current=\"page\" attribute on the anchor tag -->\n              <a class=\"govuk-navigation__nav-list-item-link\"                href=\"#components\">\n                Components\n              </a>\n            </li>            <!-- Use the \"govuk-navigation__nav-list-item--active\" modifier class on the li to visually highlight the page the user is currently viewing -->\n            <li class=\"govuk-navigation__nav-list-item \">\n              <!-- Use the aria-current=\"page\" attribute on the anchor tag -->\n              <a class=\"govuk-navigation__nav-list-item-link\"                href=\"#patterns\">\n                Patterns\n              </a>\n            </li>            <!-- Use the \"govuk-navigation__nav-list-item--active\" modifier class on the li to visually highlight the page the user is currently viewing -->\n            <li class=\"govuk-navigation__nav-list-item \">\n              <!-- Use the aria-current=\"page\" attribute on the anchor tag -->\n              <a class=\"govuk-navigation__nav-list-item-link\"                href=\"#community\">\n                Community\n              </a>\n            </li>            <!-- Use the \"govuk-navigation__nav-list-item--active\" modifier class on the li to visually highlight the page the user is currently viewing -->\n            <li class=\"govuk-navigation__nav-list-item \">\n              <!-- Use the aria-current=\"page\" attribute on the anchor tag -->\n              <a class=\"govuk-navigation__nav-list-item-link\"                href=\"#accessibility\">\n                Accessibility\n              </a>\n            </li>          </ul>\n        </nav>\n      </div>\n    </div>\n  </div>\n</div>"
+        }
+    ]
+}
diff --git a/packages/govuk-frontend/dist/govuk/components/navigation/macro-options.json b/packages/govuk-frontend/dist/govuk/components/navigation/macro-options.json
new file mode 100644
index 000000000..01b482b97
--- /dev/null
+++ b/packages/govuk-frontend/dist/govuk/components/navigation/macro-options.json
@@ -0,0 +1,28 @@
+[
+    {
+        "name": "serviceName",
+        "type": "string",
+        "required": false,
+        "description": "The name of your service."
+    },
+    {
+        "name": "items",
+        "type": "array",
+        "required": false,
+        "description": "The navigation items within the navigation component.",
+        "params": [
+            {
+                "name": "label",
+                "type": "string",
+                "required": true,
+                "description": "The displayed text for the navigation item"
+            },
+            {
+                "name": "url",
+                "type": "string",
+                "required": true,
+                "description": "The path for the url"
+            }
+        ]
+    }
+]
diff --git a/packages/govuk-frontend/dist/govuk/components/navigation/navigation.bundle.js b/packages/govuk-frontend/dist/govuk/components/navigation/navigation.bundle.js
new file mode 100644
index 000000000..b0f38d9dc
--- /dev/null
+++ b/packages/govuk-frontend/dist/govuk/components/navigation/navigation.bundle.js
@@ -0,0 +1,134 @@
+(function (global, factory) {
+  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+  typeof define === 'function' && define.amd ? define(['exports'], factory) :
+  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.GOVUKFrontend = {}));
+})(this, (function (exports) { 'use strict';
+
+  function isSupported($scope = document.body) {
+    if (!$scope) {
+      return false;
+    }
+    return $scope.classList.contains('govuk-frontend-supported');
+  }
+
+  /**
+   * Schema for component config
+   *
+   * @typedef {object} Schema
+   * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
+   * @property {SchemaCondition[]} [anyOf] - List of schema conditions
+   */
+
+  /**
+   * Schema property for component config
+   *
+   * @typedef {object} SchemaProperty
+   * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
+   */
+
+  /**
+   * Schema condition for component config
+   *
+   * @typedef {object} SchemaCondition
+   * @property {string[]} required - List of required config fields
+   * @property {string} errorMessage - Error message when required config fields not provided
+   */
+
+  class GOVUKFrontendError extends Error {
+    constructor(...args) {
+      super(...args);
+      this.name = 'GOVUKFrontendError';
+    }
+  }
+  class SupportError extends GOVUKFrontendError {
+    /**
+     * Checks if GOV.UK Frontend is supported on this page
+     *
+     * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support
+     */
+    constructor($scope = document.body) {
+      const supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet' : 'GOV.UK Frontend is not supported in this browser';
+      super($scope ? supportMessage : 'GOV.UK Frontend initialised without `<script type="module">`');
+      this.name = 'SupportError';
+    }
+  }
+  class ElementError extends GOVUKFrontendError {
+    constructor(messageOrOptions) {
+      let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
+      if (typeof messageOrOptions === 'object') {
+        const {
+          componentName,
+          identifier,
+          element,
+          expectedType
+        } = messageOrOptions;
+        message = `${componentName}: ${identifier}`;
+        message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
+      }
+      super(message);
+      this.name = 'ElementError';
+    }
+  }
+
+  class GOVUKFrontendComponent {
+    constructor() {
+      this.checkSupport();
+    }
+    checkSupport() {
+      if (!isSupported()) {
+        throw new SupportError();
+      }
+    }
+  }
+
+  class Navigation extends GOVUKFrontendComponent {
+    /**
+     * Class constructor
+     *
+     * @param {Element | null} $module - HTML element to use for header
+     */
+    constructor($module) {
+      super();
+      if (!$module) {
+        throw new ElementError({
+          componentName: 'Header',
+          element: $module,
+          identifier: 'Root element (`$module`)'
+        });
+      }
+      const $nav = $module;
+      $nav.$menuButton = $nav.querySelector('.js-x-header-toggle');
+      $nav.$menu = $nav.$menuButton && $nav.querySelector('#' + $nav.$menuButton.getAttribute('aria-controls'));
+      $nav.menuItems = $nav.$menu && $nav.$menu.querySelectorAll('li');
+      if (!$nav.$menuButton || !$nav.$menu || $nav.menuItems.length < 2) {
+        return;
+      }
+      $nav.classList.add('toggle-enabled');
+      $nav.$menuOpenClass = $nav.$menu && $nav.$menu.dataset.openClass;
+      $nav.$menuButtonOpenClass = $nav.$menuButton && $nav.$menuButton.dataset.openClass;
+      $nav.$menuButtonOpenLabel = $nav.$menuButton && $nav.$menuButton.dataset.labelForShow;
+      $nav.$menuButtonCloseLabel = $nav.$menuButton && $nav.$menuButton.dataset.labelForHide;
+      $nav.$menuButtonOpenText = $nav.$menuButton && $nav.$menuButton.dataset.textForShow;
+      $nav.$menuButtonCloseText = $nav.$menuButton && $nav.$menuButton.dataset.textForHide;
+      $nav.isOpen = false;
+      $nav.$menuButton.addEventListener('click', this.handleMenuButtonClick.bind($nav));
+    }
+    handleMenuButtonClick() {
+      this.isOpen = !this.isOpen;
+      this.$menuOpenClass && this.$menu.classList.toggle(this.$menuOpenClass, this.isOpen);
+      this.$menuButtonOpenClass && this.$menuButton.classList.toggle(this.$menuButtonOpenClass, this.isOpen);
+      this.$menuButton.setAttribute('aria-expanded', this.isOpen);
+      if (this.$menuButtonCloseLabel && this.$menuButtonOpenLabel) {
+        this.$menuButton.setAttribute('aria-label', this.isOpen ? this.$menuButtonCloseLabel : this.$menuButtonOpenLabel);
+      }
+      if (this.$menuButtonCloseText && this.$menuButtonOpenText) {
+        this.$menuButton.innerHTML = this.isOpen ? this.$menuButtonCloseText : this.$menuButtonOpenText;
+      }
+    }
+  }
+  Navigation.moduleName = 'govuk-navigation';
+
+  exports.Navigation = Navigation;
+
+}));
+//# sourceMappingURL=navigation.bundle.js.map
diff --git a/packages/govuk-frontend/dist/govuk/components/navigation/navigation.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/navigation/navigation.bundle.mjs
new file mode 100644
index 000000000..774ca32ed
--- /dev/null
+++ b/packages/govuk-frontend/dist/govuk/components/navigation/navigation.bundle.mjs
@@ -0,0 +1,126 @@
+function isSupported($scope = document.body) {
+  if (!$scope) {
+    return false;
+  }
+  return $scope.classList.contains('govuk-frontend-supported');
+}
+
+/**
+ * Schema for component config
+ *
+ * @typedef {object} Schema
+ * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
+ * @property {SchemaCondition[]} [anyOf] - List of schema conditions
+ */
+
+/**
+ * Schema property for component config
+ *
+ * @typedef {object} SchemaProperty
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
+ */
+
+/**
+ * Schema condition for component config
+ *
+ * @typedef {object} SchemaCondition
+ * @property {string[]} required - List of required config fields
+ * @property {string} errorMessage - Error message when required config fields not provided
+ */
+
+class GOVUKFrontendError extends Error {
+  constructor(...args) {
+    super(...args);
+    this.name = 'GOVUKFrontendError';
+  }
+}
+class SupportError extends GOVUKFrontendError {
+  /**
+   * Checks if GOV.UK Frontend is supported on this page
+   *
+   * @param {HTMLElement | null} [$scope] - HTML element `<body>` checked for browser support
+   */
+  constructor($scope = document.body) {
+    const supportMessage = 'noModule' in HTMLScriptElement.prototype ? 'GOV.UK Frontend initialised without `<body class="govuk-frontend-supported">` from template `<script>` snippet' : 'GOV.UK Frontend is not supported in this browser';
+    super($scope ? supportMessage : 'GOV.UK Frontend initialised without `<script type="module">`');
+    this.name = 'SupportError';
+  }
+}
+class ElementError extends GOVUKFrontendError {
+  constructor(messageOrOptions) {
+    let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
+    if (typeof messageOrOptions === 'object') {
+      const {
+        componentName,
+        identifier,
+        element,
+        expectedType
+      } = messageOrOptions;
+      message = `${componentName}: ${identifier}`;
+      message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
+    }
+    super(message);
+    this.name = 'ElementError';
+  }
+}
+
+class GOVUKFrontendComponent {
+  constructor() {
+    this.checkSupport();
+  }
+  checkSupport() {
+    if (!isSupported()) {
+      throw new SupportError();
+    }
+  }
+}
+
+class Navigation extends GOVUKFrontendComponent {
+  /**
+   * Class constructor
+   *
+   * @param {Element | null} $module - HTML element to use for header
+   */
+  constructor($module) {
+    super();
+    if (!$module) {
+      throw new ElementError({
+        componentName: 'Header',
+        element: $module,
+        identifier: 'Root element (`$module`)'
+      });
+    }
+    const $nav = $module;
+    $nav.$menuButton = $nav.querySelector('.js-x-header-toggle');
+    $nav.$menu = $nav.$menuButton && $nav.querySelector('#' + $nav.$menuButton.getAttribute('aria-controls'));
+    $nav.menuItems = $nav.$menu && $nav.$menu.querySelectorAll('li');
+    if (!$nav.$menuButton || !$nav.$menu || $nav.menuItems.length < 2) {
+      return;
+    }
+    $nav.classList.add('toggle-enabled');
+    $nav.$menuOpenClass = $nav.$menu && $nav.$menu.dataset.openClass;
+    $nav.$menuButtonOpenClass = $nav.$menuButton && $nav.$menuButton.dataset.openClass;
+    $nav.$menuButtonOpenLabel = $nav.$menuButton && $nav.$menuButton.dataset.labelForShow;
+    $nav.$menuButtonCloseLabel = $nav.$menuButton && $nav.$menuButton.dataset.labelForHide;
+    $nav.$menuButtonOpenText = $nav.$menuButton && $nav.$menuButton.dataset.textForShow;
+    $nav.$menuButtonCloseText = $nav.$menuButton && $nav.$menuButton.dataset.textForHide;
+    $nav.isOpen = false;
+    $nav.$menuButton.addEventListener('click', this.handleMenuButtonClick.bind($nav));
+  }
+  handleMenuButtonClick() {
+    this.isOpen = !this.isOpen;
+    this.$menuOpenClass && this.$menu.classList.toggle(this.$menuOpenClass, this.isOpen);
+    this.$menuButtonOpenClass && this.$menuButton.classList.toggle(this.$menuButtonOpenClass, this.isOpen);
+    this.$menuButton.setAttribute('aria-expanded', this.isOpen);
+    if (this.$menuButtonCloseLabel && this.$menuButtonOpenLabel) {
+      this.$menuButton.setAttribute('aria-label', this.isOpen ? this.$menuButtonCloseLabel : this.$menuButtonOpenLabel);
+    }
+    if (this.$menuButtonCloseText && this.$menuButtonOpenText) {
+      this.$menuButton.innerHTML = this.isOpen ? this.$menuButtonCloseText : this.$menuButtonOpenText;
+    }
+  }
+}
+Navigation.moduleName = 'govuk-navigation';
+
+export { Navigation };
+//# sourceMappingURL=navigation.bundle.mjs.map
diff --git a/packages/govuk-frontend/dist/govuk/components/navigation/navigation.mjs b/packages/govuk-frontend/dist/govuk/components/navigation/navigation.mjs
new file mode 100644
index 000000000..94801e6b6
--- /dev/null
+++ b/packages/govuk-frontend/dist/govuk/components/navigation/navigation.mjs
@@ -0,0 +1,52 @@
+import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs';
+import { ElementError } from '../../errors/index.mjs';
+
+class Navigation extends GOVUKFrontendComponent {
+  /**
+   * Class constructor
+   *
+   * @param {Element | null} $module - HTML element to use for header
+   */
+  constructor($module) {
+    super();
+    if (!$module) {
+      throw new ElementError({
+        componentName: 'Header',
+        element: $module,
+        identifier: 'Root element (`$module`)'
+      });
+    }
+    const $nav = $module;
+    $nav.$menuButton = $nav.querySelector('.js-x-header-toggle');
+    $nav.$menu = $nav.$menuButton && $nav.querySelector('#' + $nav.$menuButton.getAttribute('aria-controls'));
+    $nav.menuItems = $nav.$menu && $nav.$menu.querySelectorAll('li');
+    if (!$nav.$menuButton || !$nav.$menu || $nav.menuItems.length < 2) {
+      return;
+    }
+    $nav.classList.add('toggle-enabled');
+    $nav.$menuOpenClass = $nav.$menu && $nav.$menu.dataset.openClass;
+    $nav.$menuButtonOpenClass = $nav.$menuButton && $nav.$menuButton.dataset.openClass;
+    $nav.$menuButtonOpenLabel = $nav.$menuButton && $nav.$menuButton.dataset.labelForShow;
+    $nav.$menuButtonCloseLabel = $nav.$menuButton && $nav.$menuButton.dataset.labelForHide;
+    $nav.$menuButtonOpenText = $nav.$menuButton && $nav.$menuButton.dataset.textForShow;
+    $nav.$menuButtonCloseText = $nav.$menuButton && $nav.$menuButton.dataset.textForHide;
+    $nav.isOpen = false;
+    $nav.$menuButton.addEventListener('click', this.handleMenuButtonClick.bind($nav));
+  }
+  handleMenuButtonClick() {
+    this.isOpen = !this.isOpen;
+    this.$menuOpenClass && this.$menu.classList.toggle(this.$menuOpenClass, this.isOpen);
+    this.$menuButtonOpenClass && this.$menuButton.classList.toggle(this.$menuButtonOpenClass, this.isOpen);
+    this.$menuButton.setAttribute('aria-expanded', this.isOpen);
+    if (this.$menuButtonCloseLabel && this.$menuButtonOpenLabel) {
+      this.$menuButton.setAttribute('aria-label', this.isOpen ? this.$menuButtonCloseLabel : this.$menuButtonOpenLabel);
+    }
+    if (this.$menuButtonCloseText && this.$menuButtonOpenText) {
+      this.$menuButton.innerHTML = this.isOpen ? this.$menuButtonCloseText : this.$menuButtonOpenText;
+    }
+  }
+}
+Navigation.moduleName = 'govuk-navigation';
+
+export { Navigation };
+//# sourceMappingURL=navigation.mjs.map

Action run for 13efac5faf81336f16df5a924f2f4e3c754a08f1

CharlotteDowns commented 7 months ago

@domoscargin I'm so glad you brought this up:

Interesting challenge: on the DS website, the navigation collapses into a "menu" button on smaller screens, with that menu popping into the header:

I'm not sure I like this about it if I'm honest, I'm drafting some potential designs in the [Navigation spikes doc] to help (https://docs.google.com/document/d/1XUgtfGpnEWXjWBqouZs2FYDZrGHhhr1KPxoT68poHDA/edit#heading=h.6y0801gjvd5q)

github-actions[bot] commented 7 months ago

JavaScript changes to npm package

diff --git a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
index db99e502b..ce2b6e8da 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
@@ -1,44 +1,44 @@
 const version = "development";

 function normaliseString(e, t) {
-    const s = e ? e.trim() : "";
-    let n, i = null == t ? void 0 : t.type;
-    switch (i || (["true", "false"].includes(s) && (i = "boolean"), s.length > 0 && isFinite(Number(s)) && (i = "number")), i) {
+    const n = e ? e.trim() : "";
+    let s, i = null == t ? void 0 : t.type;
+    switch (i || (["true", "false"].includes(n) && (i = "boolean"), n.length > 0 && isFinite(Number(n)) && (i = "number")), i) {
         case "boolean":
-            n = "true" === s;
+            s = "true" === n;
             break;
         case "number":
-            n = Number(s);
+            s = Number(n);
             break;
         default:
-            n = e
+            s = e
     }
-    return n
+    return s
 }

 function mergeConfigs(...e) {
     const t = {};
-    for (const s of e)
-        for (const e of Object.keys(s)) {
-            const n = t[e],
-                i = s[e];
-            isObject(n) && isObject(i) ? t[e] = mergeConfigs(n, i) : t[e] = i
+    for (const n of e)
+        for (const e of Object.keys(n)) {
+            const s = t[e],
+                i = n[e];
+            isObject(s) && isObject(i) ? t[e] = mergeConfigs(s, i) : t[e] = i
         }
     return t
 }

-function extractConfigByNamespace(e, t, s) {
-    const n = e.schema.properties[s];
-    if ("object" !== (null == n ? void 0 : n.type)) return;
+function extractConfigByNamespace(e, t, n) {
+    const s = e.schema.properties[n];
+    if ("object" !== (null == s ? void 0 : s.type)) return;
     const i = {
-        [s]: {}
+        [n]: {}
     };
     for (const [o, r] of Object.entries(t)) {
         let e = i;
         const t = o.split(".");
-        for (const [n, i] of t.entries()) "object" == typeof e && (n < t.length - 1 ? (isObject(e[i]) || (e[i] = {}), e = e[i]) : o !== s && (e[i] = normaliseString(r)))
+        for (const [s, i] of t.entries()) "object" == typeof e && (s < t.length - 1 ? (isObject(e[i]) || (e[i] = {}), e = e[i]) : o !== n && (e[i] = normaliseString(r)))
     }
-    return i[s]
+    return i[n]
 }

 function getFragmentFromUrl(e) {
@@ -54,20 +54,20 @@ function getBreakpoint(e) {
 }

 function setFocus(e, t = {}) {
-    var s;
-    const n = e.getAttribute("tabindex");
+    var n;
+    const s = e.getAttribute("tabindex");

     function onBlur() {
-        var s;
-        null == (s = t.onBlur) || s.call(e), n || e.removeAttribute("tabindex")
+        var n;
+        null == (n = t.onBlur) || n.call(e), s || e.removeAttribute("tabindex")
     }
-    n || e.setAttribute("tabindex", "-1"), e.addEventListener("focus", (function() {
+    s || e.setAttribute("tabindex", "-1"), e.addEventListener("focus", (function() {
         e.addEventListener("blur", onBlur, {
             once: !0
         })
     }), {
         once: !0
-    }), null == (s = t.onBeforeFocus) || s.call(e), e.focus()
+    }), null == (n = t.onBeforeFocus) || n.call(e), e.focus()
 }

 function isSupported(e = document.body) {
@@ -81,9 +81,9 @@ function isObject(e) {
 }

 function normaliseDataset(e, t) {
-    const s = {};
-    for (const [n, i] of Object.entries(e.schema.properties)) n in t && (s[n] = normaliseString(t[n], i)), "object" === (null == i ? void 0 : i.type) && (s[n] = extractConfigByNamespace(e, t, n));
-    return s
+    const n = {};
+    for (const [s, i] of Object.entries(e.schema.properties)) s in t && (n[s] = normaliseString(t[s], i)), "object" === (null == i ? void 0 : i.type) && (n[s] = extractConfigByNamespace(e, t, s));
+    return n
 }
 class GOVUKFrontendError extends Error {
     constructor(...e) {
@@ -106,12 +106,12 @@ class ElementError extends GOVUKFrontendError {
         let t = "string" == typeof e ? e : "";
         if ("object" == typeof e) {
             const {
-                componentName: s,
-                identifier: n,
+                componentName: n,
+                identifier: s,
                 element: i,
                 expectedType: o
             } = e;
-            t = `${s}: ${n}`, t += i ? ` is not of type ${null!=o?o:"HTMLElement"}` : " not found"
+            t = `${n}: ${s}`, t += i ? ` is not of type ${null!=o?o:"HTMLElement"}` : " not found"
         }
         super(t), this.name = "ElementError"
     }
@@ -126,31 +126,31 @@ class GOVUKFrontendComponent {
 }
 class I18n {
     constructor(e = {}, t = {}) {
-        var s;
-        this.translations = void 0, this.locale = void 0, this.translations = e, this.locale = null != (s = t.locale) ? s : document.documentElement.lang || "en"
+        var n;
+        this.translations = void 0, this.locale = void 0, this.translations = e, this.locale = null != (n = t.locale) ? n : document.documentElement.lang || "en"
     }
     t(e, t) {
         if (!e) throw new Error("i18n: lookup key missing");
-        let s = this.translations[e];
-        if ("number" == typeof(null == t ? void 0 : t.count) && "object" == typeof s) {
-            const n = s[this.getPluralSuffix(e, t.count)];
-            n && (s = n)
+        let n = this.translations[e];
+        if ("number" == typeof(null == t ? void 0 : t.count) && "object" == typeof n) {
+            const s = n[this.getPluralSuffix(e, t.count)];
+            s && (n = s)
         }
-        if ("string" == typeof s) {
-            if (s.match(/%{(.\S+)}/)) {
+        if ("string" == typeof n) {
+            if (n.match(/%{(.\S+)}/)) {
                 if (!t) throw new Error("i18n: cannot replace placeholders in string if no option data provided");
-                return this.replacePlaceholders(s, t)
+                return this.replacePlaceholders(n, t)
             }
-            return s
+            return n
         }
         return e
     }
     replacePlaceholders(e, t) {
-        const s = Intl.NumberFormat.supportedLocalesOf(this.locale).length ? new Intl.NumberFormat(this.locale) : void 0;
-        return e.replace(/%{(.\S+)}/g, (function(e, n) {
-            if (Object.prototype.hasOwnProperty.call(t, n)) {
-                const e = t[n];
-                return !1 === e || "number" != typeof e && "string" != typeof e ? "" : "number" == typeof e ? s ? s.format(e) : `${e}` : e
+        const n = Intl.NumberFormat.supportedLocalesOf(this.locale).length ? new Intl.NumberFormat(this.locale) : void 0;
+        return e.replace(/%{(.\S+)}/g, (function(e, s) {
+            if (Object.prototype.hasOwnProperty.call(t, s)) {
+                const e = t[s];
+                return !1 === e || "number" != typeof e && "string" != typeof e ? "" : "number" == typeof e ? n ? n.format(e) : `${e}` : e
             }
             throw new Error(`i18n: no data found to replace ${e} placeholder in string`)
         }))
@@ -160,11 +160,11 @@ class I18n {
     }
     getPluralSuffix(e, t) {
         if (t = Number(t), !isFinite(t)) return "other";
-        const s = this.translations[e],
-            n = this.hasIntlPluralRulesSupport() ? new Intl.PluralRules(this.locale).select(t) : this.selectPluralFormUsingFallbackRules(t);
-        if ("object" == typeof s) {
-            if (n in s) return n;
-            if ("other" in s) return console.warn(`i18n: Missing plural form ".${n}" for "${this.locale}" locale. Falling back to ".other".`), "other"
+        const n = this.translations[e],
+            s = this.hasIntlPluralRulesSupport() ? new Intl.PluralRules(this.locale).select(t) : this.selectPluralFormUsingFallbackRules(t);
+        if ("object" == typeof n) {
+            if (s in n) return s;
+            if ("other" in n) return console.warn(`i18n: Missing plural form ".${s}" for "${this.locale}" locale. Falling back to ".other".`), "other"
         }
         throw new Error(`i18n: Plural form ".other" is required for "${this.locale}" locale`)
     }
@@ -176,8 +176,8 @@ class I18n {
     getPluralRulesForLocale() {
         const e = this.locale.split("-")[0];
         for (const t in I18n.pluralRulesMap) {
-            const s = I18n.pluralRulesMap[t];
-            if (s.includes(this.locale) || s.includes(e)) return t
+            const n = I18n.pluralRulesMap[t];
+            if (n.includes(this.locale) || n.includes(e)) return t
         }
     }
 }
@@ -199,27 +199,27 @@ I18n.pluralRulesMap = {
     irish: e => 1 === e ? "one" : 2 === e ? "two" : e >= 3 && e <= 6 ? "few" : e >= 7 && e <= 10 ? "many" : "other",
     russian(e) {
         const t = e % 100,
-            s = t % 10;
-        return 1 === s && 11 !== t ? "one" : s >= 2 && s <= 4 && !(t >= 12 && t <= 14) ? "few" : 0 === s || s >= 5 && s <= 9 || t >= 11 && t <= 14 ? "many" : "other"
+            n = t % 10;
+        return 1 === n && 11 !== t ? "one" : n >= 2 && n <= 4 && !(t >= 12 && t <= 14) ? "few" : 0 === n || n >= 5 && n <= 9 || t >= 11 && t <= 14 ? "many" : "other"
     },
     scottish: e => 1 === e || 11 === e ? "one" : 2 === e || 12 === e ? "two" : e >= 3 && e <= 10 || e >= 13 && e <= 19 ? "few" : "other",
     spanish: e => 1 === e ? "one" : e % 1e6 == 0 && 0 !== e ? "many" : "other",
     welsh: e => 0 === e ? "zero" : 1 === e ? "one" : 2 === e ? "two" : 3 === e ? "few" : 6 === e ? "many" : "other"
 };
 class Accordion extends GOVUKFrontendComponent {
-    constructor(t, s = {}) {
+    constructor(t, n = {}) {
         if (super(), this.$module = void 0, this.config = void 0, this.i18n = void 0, this.controlsClass = "govuk-accordion__controls", this.showAllClass = "govuk-accordion__show-all", this.showAllTextClass = "govuk-accordion__show-all-text", this.sectionClass = "govuk-accordion__section", this.sectionExpandedClass = "govuk-accordion__section--expanded", this.sectionButtonClass = "govuk-accordion__section-button", this.sectionHeaderClass = "govuk-accordion__section-header", this.sectionHeadingClass = "govuk-accordion__section-heading", this.sectionHeadingDividerClass = "govuk-accordion__section-heading-divider", this.sectionHeadingTextClass = "govuk-accordion__section-heading-text", this.sectionHeadingTextFocusClass = "govuk-accordion__section-heading-text-focus", this.sectionShowHideToggleClass = "govuk-accordion__section-toggle", this.sectionShowHideToggleFocusClass = "govuk-accordion__section-toggle-focus", this.sectionShowHideTextClass = "govuk-accordion__section-toggle-text", this.upChevronIconClass = "govuk-accordion-nav__chevron", this.downChevronIconClass = "govuk-accordion-nav__chevron--down", this.sectionSummaryClass = "govuk-accordion__section-summary", this.sectionSummaryFocusClass = "govuk-accordion__section-summary-focus", this.sectionContentClass = "govuk-accordion__section-content", this.$sections = void 0, this.browserSupportsSessionStorage = !1, this.$showAllButton = null, this.$showAllIcon = null, this.$showAllText = null, !(t instanceof HTMLElement)) throw new ElementError({
             componentName: "Accordion",
             element: t,
             identifier: "Root element (`$module`)"
         });
-        this.$module = t, this.config = mergeConfigs(Accordion.defaults, s, normaliseDataset(Accordion, t.dataset)), this.i18n = new I18n(this.config.i18n);
-        const n = this.$module.querySelectorAll(`.${this.sectionClass}`);
-        if (!n.length) throw new ElementError({
+        this.$module = t, this.config = mergeConfigs(Accordion.defaults, n, normaliseDataset(Accordion, t.dataset)), this.i18n = new I18n(this.config.i18n);
+        const s = this.$module.querySelectorAll(`.${this.sectionClass}`);
+        if (!s.length) throw new ElementError({
             componentName: "Accordion",
             identifier: `Sections (\`<div class="${this.sectionClass}">\`)`
         });
-        this.$sections = n, this.browserSupportsSessionStorage = e.checkForSessionStorage(), this.initControls(), this.initSectionHeaders();
+        this.$sections = s, this.browserSupportsSessionStorage = e.checkForSessionStorage(), this.initControls(), this.initSectionHeaders();
         const i = this.checkIfAllSectionsOpen();
         this.updateShowAllButton(i)
     }
@@ -230,53 +230,53 @@ class Accordion extends GOVUKFrontendComponent {
     }
     initSectionHeaders() {
         this.$sections.forEach(((e, t) => {
-            const s = e.querySelector(`.${this.sectionHeaderClass}`);
-            if (!s) throw new ElementError({
+            const n = e.querySelector(`.${this.sectionHeaderClass}`);
+            if (!n) throw new ElementError({
                 componentName: "Accordion",
                 identifier: `Section headers (\`<div class="${this.sectionHeaderClass}">\`)`
             });
-            this.constructHeaderMarkup(s, t), this.setExpanded(this.isExpanded(e), e), s.addEventListener("click", (() => this.onSectionToggle(e))), this.setInitialState(e)
+            this.constructHeaderMarkup(n, t), this.setExpanded(this.isExpanded(e), e), n.addEventListener("click", (() => this.onSectionToggle(e))), this.setInitialState(e)
         }))
     }
     constructHeaderMarkup(e, t) {
-        const s = e.querySelector(`.${this.sectionButtonClass}`),
-            n = e.querySelector(`.${this.sectionHeadingClass}`),
+        const n = e.querySelector(`.${this.sectionButtonClass}`),
+            s = e.querySelector(`.${this.sectionHeadingClass}`),
             i = e.querySelector(`.${this.sectionSummaryClass}`);
-        if (!n) throw new ElementError({
+        if (!s) throw new ElementError({
             componentName: "Accordion",
             identifier: `Section heading (\`.${this.sectionHeadingClass}\`)`
         });
-        if (!s) throw new ElementError({
+        if (!n) throw new ElementError({
             componentName: "Accordion",
             identifier: `Section button placeholder (\`<span class="${this.sectionButtonClass}">\`)`
         });
         const o = document.createElement("button");
         o.setAttribute("type", "button"), o.setAttribute("aria-controls", `${this.$module.id}-content-${t+1}`);
-        for (const d of Array.from(s.attributes)) "id" !== d.nodeName && o.setAttribute(d.nodeName, `${d.nodeValue}`);
+        for (const d of Array.from(n.attributes)) "id" !== d.nodeName && o.setAttribute(d.nodeName, `${d.nodeValue}`);
         const r = document.createElement("span");
-        r.classList.add(this.sectionHeadingTextClass), r.id = s.id;
+        r.classList.add(this.sectionHeadingTextClass), r.id = n.id;
         const a = document.createElement("span");
-        a.classList.add(this.sectionHeadingTextFocusClass), r.appendChild(a), a.innerHTML = s.innerHTML;
+        a.classList.add(this.sectionHeadingTextFocusClass), r.appendChild(a), a.innerHTML = n.innerHTML;
         const l = document.createElement("span");
         l.classList.add(this.sectionShowHideToggleClass), l.setAttribute("data-nosnippet", "");
-        const c = document.createElement("span");
-        c.classList.add(this.sectionShowHideToggleFocusClass), l.appendChild(c);
-        const h = document.createElement("span"),
-            u = document.createElement("span");
-        if (u.classList.add(this.upChevronIconClass), c.appendChild(u), h.classList.add(this.sectionShowHideTextClass), c.appendChild(h), o.appendChild(r), o.appendChild(this.getButtonPunctuationEl()), null != i && i.parentNode) {
+        const u = document.createElement("span");
+        u.classList.add(this.sectionShowHideToggleFocusClass), l.appendChild(u);
+        const c = document.createElement("span"),
+            h = document.createElement("span");
+        if (h.classList.add(this.upChevronIconClass), u.appendChild(h), c.classList.add(this.sectionShowHideTextClass), u.appendChild(c), o.appendChild(r), o.appendChild(this.getButtonPunctuationEl()), null != i && i.parentNode) {
             const e = document.createElement("span"),
                 t = document.createElement("span");
             t.classList.add(this.sectionSummaryFocusClass), e.appendChild(t);
-            for (const s of Array.from(i.attributes)) e.setAttribute(s.nodeName, `${s.nodeValue}`);
+            for (const n of Array.from(i.attributes)) e.setAttribute(n.nodeName, `${n.nodeValue}`);
             t.innerHTML = i.innerHTML, i.parentNode.replaceChild(e, i), o.appendChild(e), o.appendChild(this.getButtonPunctuationEl())
         }
-        o.appendChild(l), n.removeChild(s), n.appendChild(o)
+        o.appendChild(l), s.removeChild(n), s.appendChild(o)
     }
     onBeforeMatch(e) {
         const t = e.target;
         if (!(t instanceof Element)) return;
-        const s = t.closest(`.${this.sectionClass}`);
-        s && this.setExpanded(!0, s)
+        const n = t.closest(`.${this.sectionClass}`);
+        n && this.setExpanded(!0, n)
     }
     onSectionToggle(e) {
         const t = this.isExpanded(e);
@@ -289,26 +289,26 @@ class Accordion extends GOVUKFrontendComponent {
         })), this.updateShowAllButton(e)
     }
     setExpanded(e, t) {
-        const s = t.querySelector(`.${this.upChevronIconClass}`),
-            n = t.querySelector(`.${this.sectionShowHideTextClass}`),
+        const n = t.querySelector(`.${this.upChevronIconClass}`),
+            s = t.querySelector(`.${this.sectionShowHideTextClass}`),
             i = t.querySelector(`.${this.sectionButtonClass}`),
             o = t.querySelector(`.${this.sectionContentClass}`);
         if (!o) throw new ElementError({
             componentName: "Accordion",
             identifier: `Section content (\`<div class="${this.sectionContentClass}">\`)`
         });
-        if (!s || !n || !i) return;
+        if (!n || !s || !i) return;
         const r = e ? this.i18n.t("hideSection") : this.i18n.t("showSection");
-        n.textContent = r, i.setAttribute("aria-expanded", `${e}`);
+        s.textContent = r, i.setAttribute("aria-expanded", `${e}`);
         const a = [],
             l = t.querySelector(`.${this.sectionHeadingTextClass}`);
         l && a.push(`${l.textContent}`.trim());
-        const c = t.querySelector(`.${this.sectionSummaryClass}`);
-        c && a.push(`${c.textContent}`.trim());
-        const h = e ? this.i18n.t("hideSectionAriaLabel") : this.i18n.t("showSectionAriaLabel");
-        a.push(h), i.setAttribute("aria-label", a.join(" , ")), e ? (o.removeAttribute("hidden"), t.classList.add(this.sectionExpandedClass), s.classList.remove(this.downChevronIconClass)) : (o.setAttribute("hidden", "until-found"), t.classList.remove(this.sectionExpandedClass), s.classList.add(this.downChevronIconClass));
-        const u = this.checkIfAllSectionsOpen();
-        this.updateShowAllButton(u)
+        const u = t.querySelector(`.${this.sectionSummaryClass}`);
+        u && a.push(`${u.textContent}`.trim());
+        const c = e ? this.i18n.t("hideSectionAriaLabel") : this.i18n.t("showSectionAriaLabel");
+        a.push(c), i.setAttribute("aria-label", a.join(" , ")), e ? (o.removeAttribute("hidden"), t.classList.add(this.sectionExpandedClass), n.classList.remove(this.downChevronIconClass)) : (o.setAttribute("hidden", "until-found"), t.classList.remove(this.sectionExpandedClass), n.classList.add(this.downChevronIconClass));
+        const h = this.checkIfAllSectionsOpen();
+        this.updateShowAllButton(h)
     }
     isExpanded(e) {
         return e.classList.contains(this.sectionExpandedClass)
@@ -324,8 +324,8 @@ class Accordion extends GOVUKFrontendComponent {
             const t = e.querySelector(`.${this.sectionButtonClass}`);
             if (t) {
                 const e = t.getAttribute("aria-controls"),
-                    s = t.getAttribute("aria-expanded");
-                e && s && window.sessionStorage.setItem(e, s)
+                    n = t.getAttribute("aria-expanded");
+                e && n && window.sessionStorage.setItem(e, n)
             }
         }
     }
@@ -333,9 +333,9 @@ class Accordion extends GOVUKFrontendComponent {
         if (this.browserSupportsSessionStorage && this.config.rememberExpanded) {
             const t = e.querySelector(`.${this.sectionButtonClass}`);
             if (t) {
-                const s = t.getAttribute("aria-controls"),
-                    n = s ? window.sessionStorage.getItem(s) : null;
-                null !== n && this.setExpanded("true" === n, e)
+                const n = t.getAttribute("aria-controls"),
+                    s = n ? window.sessionStorage.getItem(n) : null;
+                null !== s && this.setExpanded("true" === s, e)
             }
         }
     }
@@ -370,7 +370,7 @@ const e = {
         let t;
         try {
             return window.sessionStorage.setItem(e, e), t = window.sessionStorage.getItem(e) === e.toString(), window.sessionStorage.removeItem(e), t
-        } catch (s) {
+        } catch (n) {
             return !1
         }
     }
@@ -396,8 +396,8 @@ class Button extends GOVUKFrontendComponent {
 }

 function closestAttributeValue(e, t) {
-    const s = e.closest(`[${t}]`);
-    return s ? s.getAttribute(t) : null
+    const n = e.closest(`[${t}]`);
+    return n ? n.getAttribute(t) : null
 }
 Button.moduleName = "govuk-button", Button.defaults = Object.freeze({
     preventDoubleClick: !1
@@ -410,7 +410,7 @@ Button.moduleName = "govuk-button", Button.defaults = Object.freeze({
 });
 class CharacterCount extends GOVUKFrontendComponent {
     constructor(e, t = {}) {
-        var s, n;
+        var n, s;
         if (super(), this.$module = void 0, this.$textarea = void 0, this.$visibleCountMessage = void 0, this.$screenReaderCountMessage = void 0, this.lastInputTimestamp = null, this.lastInputValue = "", this.valueChecker = null, this.config = void 0, this.i18n = void 0, this.maxLength = void 0, !(e instanceof HTMLElement)) throw new ElementError({
             componentName: "Character count",
             element: e,
@@ -430,38 +430,38 @@ class CharacterCount extends GOVUKFrontendComponent {
             maxwords: void 0
         }), this.config = mergeConfigs(CharacterCount.defaults, t, r, o);
         const a = function(e, t) {
-            const s = [];
-            for (const [n, i] of Object.entries(e)) {
+            const n = [];
+            for (const [s, i] of Object.entries(e)) {
                 const e = [];
                 if (Array.isArray(i)) {
                     for (const {
-                            required: s,
-                            errorMessage: n
+                            required: n,
+                            errorMessage: s
                         }
-                        of i) s.every((e => !!t[e])) || e.push(n);
-                    "anyOf" !== n || i.length - e.length >= 1 || s.push(...e)
+                        of i) n.every((e => !!t[e])) || e.push(s);
+                    "anyOf" !== s || i.length - e.length >= 1 || n.push(...e)
                 }
             }
-            return s
+            return n
         }(CharacterCount.schema, this.config);
         if (a[0]) throw new ConfigError(`Character count: ${a[0]}`);
         this.i18n = new I18n(this.config.i18n, {
             locale: closestAttributeValue(e, "lang")
-        }), this.maxLength = null != (s = null != (n = this.config.maxwords) ? n : this.config.maxlength) ? s : 1 / 0, this.$module = e, this.$textarea = i;
+        }), this.maxLength = null != (n = null != (s = this.config.maxwords) ? s : this.config.maxlength) ? n : 1 / 0, this.$module = e, this.$textarea = i;
         const l = `${this.$textarea.id}-info`,
-            c = document.getElementById(l);
-        if (!c) throw new ElementError({
+            u = document.getElementById(l);
+        if (!u) throw new ElementError({
             componentName: "Character count",
-            element: c,
+            element: u,
             identifier: `Count message (\`id="${l}"\`)`
         });
-        `${c.textContent}`.match(/^\s*$/) && (c.textContent = this.i18n.t("textareaDescription", {
+        `${u.textContent}`.match(/^\s*$/) && (u.textContent = this.i18n.t("textareaDescription", {
             count: this.maxLength
-        })), this.$textarea.insertAdjacentElement("afterend", c);
+        })), this.$textarea.insertAdjacentElement("afterend", u);
+        const c = document.createElement("div");
+        c.className = "govuk-character-count__sr-status govuk-visually-hidden", c.setAttribute("aria-live", "polite"), this.$screenReaderCountMessage = c, u.insertAdjacentElement("afterend", c);
         const h = document.createElement("div");
-        h.className = "govuk-character-count__sr-status govuk-visually-hidden", h.setAttribute("aria-live", "polite"), this.$screenReaderCountMessage = h, c.insertAdjacentElement("afterend", h);
-        const u = document.createElement("div");
-        u.className = c.className, u.classList.add("govuk-character-count__status"), u.setAttribute("aria-hidden", "true"), this.$visibleCountMessage = u, c.insertAdjacentElement("afterend", u), c.classList.add("govuk-visually-hidden"), this.$textarea.removeAttribute("maxlength"), this.bindChangeEvents(), window.addEventListener("pageshow", (() => this.updateCountMessage())), this.updateCountMessage()
+        h.className = u.className, h.classList.add("govuk-character-count__status"), h.setAttribute("aria-hidden", "true"), this.$visibleCountMessage = h, u.insertAdjacentElement("afterend", h), u.classList.add("govuk-visually-hidden"), this.$textarea.removeAttribute("maxlength"), this.bindChangeEvents(), window.addEventListener("pageshow", (() => this.updateCountMessage())), this.updateCountMessage()
     }
     bindChangeEvents() {
         this.$textarea.addEventListener("keyup", (() => this.handleKeyUp())), this.$textarea.addEventListener("focus", (() => this.handleFocus())), this.$textarea.addEventListener("blur", (() => this.handleBlur()))
@@ -504,8 +504,8 @@ class CharacterCount extends GOVUKFrontendComponent {
     }
     formatCountMessage(e, t) {
         if (0 === e) return this.i18n.t(`${t}AtLimit`);
-        const s = e < 0 ? "OverLimit" : "UnderLimit";
-        return this.i18n.t(`${t}${s}`, {
+        const n = e < 0 ? "OverLimit" : "UnderLimit";
+        return this.i18n.t(`${t}${n}`, {
             count: Math.abs(e)
         })
     }
@@ -592,10 +592,10 @@ class Checkboxes extends GOVUKFrontendComponent {
     syncConditionalRevealWithInputState(e) {
         const t = e.getAttribute("aria-controls");
         if (!t) return;
-        const s = document.getElementById(t);
-        if (null != s && s.classList.contains("govuk-checkboxes__conditional")) {
+        const n = document.getElementById(t);
+        if (null != n && n.classList.contains("govuk-checkboxes__conditional")) {
             const t = e.checked;
-            e.setAttribute("aria-expanded", t.toString()), s.classList.toggle("govuk-checkboxes__conditional--hidden", !t)
+            e.setAttribute("aria-expanded", t.toString()), n.classList.toggle("govuk-checkboxes__conditional--hidden", !t)
         }
     }
     unCheckAllInputsExcept(e) {
@@ -633,25 +633,25 @@ class ErrorSummary extends GOVUKFrontendComponent {
         if (!(e instanceof HTMLAnchorElement)) return !1;
         const t = getFragmentFromUrl(e.href);
         if (!t) return !1;
-        const s = document.getElementById(t);
-        if (!s) return !1;
-        const n = this.getAssociatedLegendOrLabel(s);
-        return !!n && (n.scrollIntoView(), s.focus({
+        const n = document.getElementById(t);
+        if (!n) return !1;
+        const s = this.getAssociatedLegendOrLabel(n);
+        return !!s && (s.scrollIntoView(), n.focus({
             preventScroll: !0
         }), !0)
     }
     getAssociatedLegendOrLabel(e) {
         var t;
-        const s = e.closest("fieldset");
-        if (s) {
-            const t = s.getElementsByTagName("legend");
+        const n = e.closest("fieldset");
+        if (n) {
+            const t = n.getElementsByTagName("legend");
             if (t.length) {
-                const s = t[0];
-                if (e instanceof HTMLInputElement && ("checkbox" === e.type || "radio" === e.type)) return s;
-                const n = s.getBoundingClientRect().top,
+                const n = t[0];
+                if (e instanceof HTMLInputElement && ("checkbox" === e.type || "radio" === e.type)) return n;
+                const s = n.getBoundingClientRect().top,
                     i = e.getBoundingClientRect();
                 if (i.height && window.innerHeight) {
-                    if (i.top + i.height - n < window.innerHeight / 2) return s
+                    if (i.top + i.height - s < window.innerHeight / 2) return n
                 }
             }
         }
@@ -674,16 +674,16 @@ class ExitThisPage extends GOVUKFrontendComponent {
             element: e,
             identifier: "Root element (`$module`)"
         });
-        const s = e.querySelector(".govuk-exit-this-page__button");
-        if (!(s instanceof HTMLAnchorElement)) throw new ElementError({
+        const n = e.querySelector(".govuk-exit-this-page__button");
+        if (!(n instanceof HTMLAnchorElement)) throw new ElementError({
             componentName: "Exit this page",
-            element: s,
+            element: n,
             expectedType: "HTMLAnchorElement",
             identifier: "Button (`.govuk-exit-this-page__button`)"
         });
-        this.config = mergeConfigs(ExitThisPage.defaults, t, normaliseDataset(ExitThisPage, e.dataset)), this.i18n = new I18n(this.config.i18n), this.$module = e, this.$button = s;
-        const n = document.querySelector(".govuk-js-exit-this-page-skiplink");
-        n instanceof HTMLAnchorElement && (this.$skiplinkButton = n), this.buildIndicator(), this.initUpdateSpan(), this.initButtonClickHandler(), "govukFrontendExitThisPageKeypress" in document.body.dataset || (document.addEventListener("keyup", this.handleKeypress.bind(this), !0), document.body.dataset.govukFrontendExitThisPageKeypress = "true"), window.addEventListener("pageshow", this.resetPage.bind(this))
+        this.config = mergeConfigs(ExitThisPage.defaults, t, normaliseDataset(ExitThisPage, e.dataset)), this.i18n = new I18n(this.config.i18n), this.$module = e, this.$button = n;
+        const s = document.querySelector(".govuk-js-exit-this-page-skiplink");
+        s instanceof HTMLAnchorElement && (this.$skiplinkButton = s), this.buildIndicator(), this.initUpdateSpan(), this.initButtonClickHandler(), "govukFrontendExitThisPageKeypress" in document.body.dataset || (document.addEventListener("keyup", this.handleKeypress.bind(this), !0), document.body.dataset.govukFrontendExitThisPageKeypress = "true"), window.addEventListener("pageshow", this.resetPage.bind(this))
     }
     initUpdateSpan() {
         this.$updateSpan = document.createElement("span"), this.$updateSpan.setAttribute("role", "status"), this.$updateSpan.className = "govuk-visually-hidden", this.$module.appendChild(this.$updateSpan)
@@ -754,18 +754,18 @@ class Header extends GOVUKFrontendComponent {
         this.$module = e;
         const t = e.querySelector(".govuk-js-header-toggle");
         if (!t) return this;
-        const s = t.getAttribute("aria-controls");
-        if (!s) throw new ElementError({
+        const n = t.getAttribute("aria-controls");
+        if (!n) throw new ElementError({
             componentName: "Header",
             identifier: 'Navigation button (`<button class="govuk-js-header-toggle">`) attribute (`aria-controls`)'
         });
-        const n = document.getElementById(s);
-        if (!n) throw new ElementError({
+        const s = document.getElementById(n);
+        if (!s) throw new ElementError({
             componentName: "Header",
-            element: n,
-            identifier: `Navigation (\`<ul id="${s}">\`)`
+            element: s,
+            identifier: `Navigation (\`<ul id="${n}">\`)`
         });
-        this.$menu = n, this.$menuButton = t, this.setupResponsiveChecks(), this.$menuButton.addEventListener("click", (() => this.handleMenuButtonClick()))
+        this.$menu = s, this.$menuButton = t, this.setupResponsiveChecks(), this.$menuButton.addEventListener("click", (() => this.handleMenuButtonClick()))
     }
     setupResponsiveChecks() {
         const e = getBreakpoint("desktop");
@@ -783,6 +783,21 @@ class Header extends GOVUKFrontendComponent {
     }
 }
 Header.moduleName = "govuk-header";
+class Navigation extends GOVUKFrontendComponent {
+    constructor(e) {
+        if (super(), !e) throw new ElementError({
+            componentName: "Header",
+            element: e,
+            identifier: "Root element (`$module`)"
+        });
+        const t = e;
+        t.$menuButton = t.querySelector(".js-x-header-toggle"), t.$menu = t.$menuButton && t.querySelector("#" + t.$menuButton.getAttribute("aria-controls")), t.menuItems = t.$menu && t.$menu.querySelectorAll("li"), !t.$menuButton || !t.$menu || t.menuItems.length < 2 || (t.classList.add("toggle-enabled"), t.$menuOpenClass = t.$menu && t.$menu.dataset.openClass, t.$menuButtonOpenClass = t.$menuButton && t.$menuButton.dataset.openClass, t.$menuButtonOpenLabel = t.$menuButton && t.$menuButton.dataset.labelForShow, t.$menuButtonCloseLabel = t.$menuButton && t.$menuButton.dataset.labelForHide, t.$menuButtonOpenText = t.$menuButton && t.$menuButton.dataset.textForShow, t.$menuButtonCloseText = t.$menuButton && t.$menuButton.dataset.textForHide, t.isOpen = !1, t.$menuButton.addEventListener("click", this.handleMenuButtonClick.bind(t)))
+    }
+    handleMenuButtonClick() {
+        this.isOpen = !this.isOpen, this.$menuOpenClass && this.$menu.classList.toggle(this.$menuOpenClass, this.isOpen), this.$menuButtonOpenClass && this.$menuButton.classList.toggle(this.$menuButtonOpenClass, this.isOpen), this.$menuButton.setAttribute("aria-expanded", this.isOpen), this.$menuButtonCloseLabel && this.$menuButtonOpenLabel && this.$menuButton.setAttribute("aria-label", this.isOpen ? this.$menuButtonCloseLabel : this.$menuButtonOpenLabel), this.$menuButtonCloseText && this.$menuButtonOpenText && (this.$menuButton.innerHTML = this.isOpen ? this.$menuButtonCloseText : this.$menuButtonOpenText)
+    }
+}
+Navigation.moduleName = "govuk-navigation";
 class NotificationBanner extends GOVUKFrontendComponent {
     constructor(e, t = {}) {
         if (super(), this.$module = void 0, this.config = void 0, !(e instanceof HTMLElement)) throw new ElementError({
@@ -809,23 +824,23 @@ class PasswordInput extends GOVUKFrontendComponent {
             element: e,
             identifier: "Root element (`$module`)"
         });
-        const s = e.querySelector(".govuk-js-password-input-input");
-        if (!(s instanceof HTMLInputElement)) throw new ElementError({
+        const n = e.querySelector(".govuk-js-password-input-input");
+        if (!(n instanceof HTMLInputElement)) throw new ElementError({
             componentName: "Password input",
-            element: s,
+            element: n,
             expectedType: "HTMLInputElement",
             identifier: "Form field (`.govuk-js-password-input-input`)"
         });
-        if ("password" !== s.type) throw new ElementError("Password input: Form field (`.govuk-js-password-input-input`) must be of type `password`.");
-        const n = e.querySelector(".govuk-js-password-input-toggle");
-        if (!(n instanceof HTMLButtonElement)) throw new ElementError({
+        if ("password" !== n.type) throw new ElementError("Password input: Form field (`.govuk-js-password-input-input`) must be of type `password`.");
+        const s = e.querySelector(".govuk-js-password-input-toggle");
+        if (!(s instanceof HTMLButtonElement)) throw new ElementError({
             componentName: "Password input",
-            element: n,
+            element: s,
             expectedType: "HTMLButtonElement",
             identifier: "Button (`.govuk-js-password-input-toggle`)"
         });
-        if ("button" !== n.type) throw new ElementError("Password input: Button (`.govuk-js-password-input-toggle`) must be of type `button`.");
-        this.$module = e, this.$input = s, this.$showHideButton = n, this.config = mergeConfigs(PasswordInput.defaults, t, normaliseDataset(PasswordInput, e.dataset)), this.i18n = new I18n(this.config.i18n, {
+        if ("button" !== s.type) throw new ElementError("Password input: Button (`.govuk-js-password-input-toggle`) must be of type `button`.");
+        this.$module = e, this.$input = n, this.$showHideButton = s, this.config = mergeConfigs(PasswordInput.defaults, t, normaliseDataset(PasswordInput, e.dataset)), this.i18n = new I18n(this.config.i18n, {
             locale: closestAttributeValue(e, "lang")
         }), this.$showHideButton.removeAttribute("hidden");
         const i = document.createElement("div");
@@ -846,9 +861,9 @@ class PasswordInput extends GOVUKFrontendComponent {
         if (e === this.$input.type) return;
         this.$input.setAttribute("type", e);
         const t = "password" === e,
-            s = t ? "show" : "hide",
-            n = t ? "passwordHidden" : "passwordShown";
-        this.$showHideButton.innerText = this.i18n.t(`${s}Password`), this.$showHideButton.setAttribute("aria-label", this.i18n.t(`${s}PasswordAriaLabel`)), this.$screenReaderStatusMessage.innerText = this.i18n.t(`${n}Announcement`)
+            n = t ? "show" : "hide",
+            s = t ? "passwordHidden" : "passwordShown";
+        this.$showHideButton.innerText = this.i18n.t(`${n}Password`), this.$showHideButton.setAttribute("aria-label", this.i18n.t(`${n}PasswordAriaLabel`)), this.$screenReaderStatusMessage.innerText = this.i18n.t(`${s}Announcement`)
     }
 }
 PasswordInput.moduleName = "govuk-password-input", PasswordInput.defaults = Object.freeze({
@@ -896,20 +911,20 @@ class Radios extends GOVUKFrontendComponent {
     syncConditionalRevealWithInputState(e) {
         const t = e.getAttribute("aria-controls");
         if (!t) return;
-        const s = document.getElementById(t);
-        if (null != s && s.classList.contains("govuk-radios__conditional")) {
+        const n = document.getElementById(t);
+        if (null != n && n.classList.contains("govuk-radios__conditional")) {
             const t = e.checked;
-            e.setAttribute("aria-expanded", t.toString()), s.classList.toggle("govuk-radios__conditional--hidden", !t)
+            e.setAttribute("aria-expanded", t.toString()), n.classList.toggle("govuk-radios__conditional--hidden", !t)
         }
     }
     handleClick(e) {
         const t = e.target;
         if (!(t instanceof HTMLInputElement) || "radio" !== t.type) return;
-        const s = document.querySelectorAll('input[type="radio"][aria-controls]'),
-            n = t.form,
+        const n = document.querySelectorAll('input[type="radio"][aria-controls]'),
+            s = t.form,
             i = t.name;
-        s.forEach((e => {
-            const t = e.form === n;
+        n.forEach((e => {
+            const t = e.form === s;
             e.name === i && t && this.syncConditionalRevealWithInputState(e)
         }))
     }
@@ -925,17 +940,17 @@ class SkipLink extends GOVUKFrontendComponent {
             identifier: "Root element (`$module`)"
         });
         this.$module = e;
-        const s = this.$module.hash,
-            n = null != (t = this.$module.getAttribute("href")) ? t : "";
+        const n = this.$module.hash,
+            s = null != (t = this.$module.getAttribute("href")) ? t : "";
         let i;
         try {
             i = new window.URL(this.$module.href)
         } catch (a) {
-            throw new ElementError(`Skip link: Target link (\`href="${n}"\`) is invalid`)
+            throw new ElementError(`Skip link: Target link (\`href="${s}"\`) is invalid`)
         }
         if (i.origin !== window.location.origin || i.pathname !== window.location.pathname) return;
-        const o = getFragmentFromUrl(s);
-        if (!o) throw new ElementError(`Skip link: Target link (\`href="${n}"\`) has no hash fragment`);
+        const o = getFragmentFromUrl(n);
+        if (!o) throw new ElementError(`Skip link: Target link (\`href="${s}"\`) has no hash fragment`);
         const r = document.getElementById(o);
         if (!r) throw new ElementError({
             componentName: "Skip link",
@@ -966,17 +981,17 @@ class Tabs extends GOVUKFrontendComponent {
             identifier: 'Links (`<a class="govuk-tabs__tab">`)'
         });
         this.$module = e, this.$tabs = t, this.boundTabClick = this.onTabClick.bind(this), this.boundTabKeydown = this.onTabKeydown.bind(this), this.boundOnHashChange = this.onHashChange.bind(this);
-        const s = this.$module.querySelector(".govuk-tabs__list"),
-            n = this.$module.querySelectorAll("li.govuk-tabs__list-item");
-        if (!s) throw new ElementError({
+        const n = this.$module.querySelector(".govuk-tabs__list"),
+            s = this.$module.querySelectorAll("li.govuk-tabs__list-item");
+        if (!n) throw new ElementError({
             componentName: "Tabs",
             identifier: 'List (`<ul class="govuk-tabs__list">`)'
         });
-        if (!n.length) throw new ElementError({
+        if (!s.length) throw new ElementError({
             componentName: "Tabs",
             identifier: 'List items (`<li class="govuk-tabs__list-item">`)'
         });
-        this.$tabList = s, this.$tabListItems = n, this.setupResponsiveChecks()
+        this.$tabList = n, this.$tabListItems = s, this.setupResponsiveChecks()
     }
     setupResponsiveChecks() {
         const e = getBreakpoint("tablet");
@@ -1012,8 +1027,8 @@ class Tabs extends GOVUKFrontendComponent {
             t = this.getTab(e);
         if (!t) return;
         if (this.changingHash) return void(this.changingHash = !1);
-        const s = this.getCurrentTab();
-        s && (this.hideTab(s), this.showTab(t), t.focus())
+        const n = this.getCurrentTab();
+        n && (this.hideTab(n), this.showTab(t), t.focus())
     }
     hideTab(e) {
         this.unhighlightTab(e), this.hidePanel(e)
@@ -1028,8 +1043,8 @@ class Tabs extends GOVUKFrontendComponent {
         const t = getFragmentFromUrl(e.href);
         if (!t) return;
         e.setAttribute("id", `tab_${t}`), e.setAttribute("role", "tab"), e.setAttribute("aria-controls", t), e.setAttribute("aria-selected", "false"), e.setAttribute("tabindex", "-1");
-        const s = this.getPanel(e);
-        s && (s.setAttribute("role", "tabpanel"), s.setAttribute("aria-labelledby", e.id), s.classList.add(this.jsHiddenClass))
+        const n = this.getPanel(e);
+        n && (n.setAttribute("role", "tabpanel"), n.setAttribute("aria-labelledby", e.id), n.classList.add(this.jsHiddenClass))
     }
     unsetAttributes(e) {
         e.removeAttribute("id"), e.removeAttribute("role"), e.removeAttribute("aria-controls"), e.removeAttribute("aria-selected"), e.removeAttribute("tabindex");
@@ -1038,14 +1053,14 @@ class Tabs extends GOVUKFrontendComponent {
     }
     onTabClick(e) {
         const t = this.getCurrentTab(),
-            s = e.currentTarget;
-        t && s instanceof HTMLAnchorElement && (e.preventDefault(), this.hideTab(t), this.showTab(s), this.createHistoryEntry(s))
+            n = e.currentTarget;
+        t && n instanceof HTMLAnchorElement && (e.preventDefault(), this.hideTab(t), this.showTab(n), this.createHistoryEntry(n))
     }
     createHistoryEntry(e) {
         const t = this.getPanel(e);
         if (!t) return;
-        const s = t.id;
-        t.id = "", this.changingHash = !0, window.location.hash = s, t.id = s
+        const n = t.id;
+        t.id = "", this.changingHash = !0, window.location.hash = n, t.id = n
     }
     onTabKeydown(e) {
         switch (e.key) {
@@ -1067,16 +1082,16 @@ class Tabs extends GOVUKFrontendComponent {
         if (null == e || !e.parentElement) return;
         const t = e.parentElement.nextElementSibling;
         if (!t) return;
-        const s = t.querySelector("a.govuk-tabs__tab");
-        s && (this.hideTab(e), this.showTab(s), s.focus(), this.createHistoryEntry(s))
+        const n = t.querySelector("a.govuk-tabs__tab");
+        n && (this.hideTab(e), this.showTab(n), n.focus(), this.createHistoryEntry(n))
     }
     activatePreviousTab() {
         const e = this.getCurrentTab();
         if (null == e || !e.parentElement) return;
         const t = e.parentElement.previousElementSibling;
         if (!t) return;
-        const s = t.querySelector("a.govuk-tabs__tab");
-        s && (this.hideTab(e), this.showTab(s), s.focus(), this.createHistoryEntry(s))
+        const n = t.querySelector("a.govuk-tabs__tab");
+        n && (this.hideTab(e), this.showTab(n), n.focus(), this.createHistoryEntry(n))
     }
     getPanel(e) {
         const t = getFragmentFromUrl(e.href);
@@ -1104,7 +1119,7 @@ class Tabs extends GOVUKFrontendComponent {
 function initAll(e) {
     var t;
     if (e = void 0 !== e ? e : {}, !isSupported()) return void console.log(new SupportError);
-    const s = [
+    const n = [
             [Accordion, e.accordion],
             [Button, e.button],
             [CharacterCount, e.characterCount],
@@ -1112,19 +1127,20 @@ function initAll(e) {
             [ErrorSummary, e.errorSummary],
             [ExitThisPage, e.exitThisPage],
             [Header],
+            [Navigation],
             [NotificationBanner, e.notificationBanner],
             [PasswordInput, e.passwordInput],
             [Radios],
             [SkipLink],
             [Tabs]
         ],
-        n = null != (t = e.scope) ? t : document;
-    s.forEach((([e, t]) => {
-        n.querySelectorAll(`[data-module="${e.moduleName}"]`).forEach((s => {
+        s = null != (t = e.scope) ? t : document;
+    n.forEach((([e, t]) => {
+        s.querySelectorAll(`[data-module="${e.moduleName}"]`).forEach((n => {
             try {
-                "defaults" in e ? new e(s, t) : new e(s)
-            } catch (n) {
-                console.log(n)
+                "defaults" in e ? new e(n, t) : new e(n)
+            } catch (s) {
+                console.log(s)
             }
         }))
     }))
@@ -1138,6 +1154,7 @@ export {
     ErrorSummary,
     ExitThisPage,
     Header,
+    Navigation,
     NotificationBanner,
     PasswordInput,
     Radios,

Action run for 13efac5faf81336f16df5a924f2f4e3c754a08f1

domoscargin commented 7 months ago

Closing in favour of #4915