diff --git a/packages/govuk-frontend-review/src/views/examples/exit-this-page-with-skiplink/index.njk b/packages/govuk-frontend-review/src/views/examples/exit-this-page-with-skiplink/index.njk
index 898d54a3fe..419c2a6e57 100644
--- a/packages/govuk-frontend-review/src/views/examples/exit-this-page-with-skiplink/index.njk
+++ b/packages/govuk-frontend-review/src/views/examples/exit-this-page-with-skiplink/index.njk
@@ -20,3 +20,19 @@
redirectUrl: "https://www.gov.uk/"
}) }}
{% endblock %}
+
+{% block bodyEnd %}
+
+
+{% endblock %}
diff --git a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.mjs b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.mjs
index fbac41f9a9..c81f2e8eab 100644
--- a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.mjs
+++ b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.mjs
@@ -36,24 +36,42 @@ export class SkipLink extends GOVUKFrontendComponent {
}
this.$module = $module
- this.$linkedElement = this.getLinkedElement()
- this.$module.addEventListener('click', () => this.focusLinkedElement())
- }
+ const hash = this.$module.hash
+ const href = this.$module.getAttribute('href') ?? ''
+
+ /** @type {URL | undefined} */
+ let url
+
+ /**
+ * Check for valid link URL
+ *
+ * {@link https://caniuse.com/url}
+ * {@link https://url.spec.whatwg.org}
+ *
+ */
+ try {
+ url = new window.URL(this.$module.href)
+ } catch (error) {
+ throw new ElementError(
+ `Skip link: Target link (\`href="${href}"\`) is invalid`
+ )
+ }
- /**
- * Get linked element
- *
- * @private
- * @returns {HTMLElement} $linkedElement - Target of the skip link
- */
- getLinkedElement() {
- const linkedElementId = getFragmentFromUrl(this.$module.hash)
+ // Return early for external URLs or links to other pages
+ if (
+ url.origin !== window.location.origin ||
+ url.pathname !== window.location.pathname
+ ) {
+ return
+ }
+
+ const linkedElementId = getFragmentFromUrl(hash)
- // Check for link hash fragment
+ // Check link path matching current page
if (!linkedElementId) {
throw new ElementError(
- 'Skip link: Root element (`$module`) attribute (`href`) has no URL fragment'
+ `Skip link: Target link (\`href="${href}"\`) has no hash fragment`
)
}
@@ -68,7 +86,9 @@ export class SkipLink extends GOVUKFrontendComponent {
})
}
- return $linkedElement
+ this.$linkedElement = $linkedElement
+
+ this.$module.addEventListener('click', () => this.focusLinkedElement())
}
/**
@@ -79,6 +99,10 @@ export class SkipLink extends GOVUKFrontendComponent {
* @private
*/
focusLinkedElement() {
+ if (!this.$linkedElement) {
+ return
+ }
+
if (!this.$linkedElement.getAttribute('tabindex')) {
// Set the element tabindex to -1 so it can be focused with JavaScript.
this.$linkedElement.setAttribute('tabindex', '-1')
@@ -107,6 +131,10 @@ export class SkipLink extends GOVUKFrontendComponent {
* @private
*/
removeFocusProperties() {
+ if (!this.$linkedElement) {
+ return
+ }
+
this.$linkedElement.removeAttribute('tabindex')
this.$linkedElement.classList.remove('govuk-skip-link-focused-element')
}
diff --git a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js
index 55b0ae2ce6..cd3b7f8cc1 100644
--- a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js
+++ b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js
@@ -67,6 +67,42 @@ describe('Skip Link', () => {
})
describe('errors at instantiation', () => {
+ it('can return early without errors for external href', async () => {
+ await render(page, 'skip-link', {
+ context: {
+ text: 'Exit this page',
+ href: 'https://www.bbc.co.uk/weather'
+ }
+ })
+ })
+
+ it('can return early without errors when linking to another page (without hash fragment)', async () => {
+ await render(page, 'skip-link', {
+ context: {
+ text: 'Exit this page',
+ href: '/clear-session-data'
+ }
+ })
+ })
+
+ it('can return early without errors when linking to another page (with hash fragment)', async () => {
+ await render(page, 'skip-link', {
+ context: {
+ text: 'Skip to main content',
+ href: '/somewhere-else#main-content'
+ }
+ })
+ })
+
+ it('can return early without errors when linking to the current page (with hash fragment)', async () => {
+ await render(page, 'skip-link', {
+ context: {
+ text: 'Skip to main content',
+ href: '#content'
+ }
+ })
+ })
+
it('can throw a SupportError if appropriate', async () => {
await expect(
render(page, 'skip-link', examples.default, {
@@ -137,14 +173,14 @@ describe('Skip Link', () => {
render(page, 'skip-link', {
context: {
text: 'Skip to main content',
- href: 'this-element-does-not-exist'
+ href: '/components/skip-link/preview'
}
})
).rejects.toMatchObject({
cause: {
name: 'ElementError',
message:
- 'Skip link: Root element (`$module`) attribute (`href`) has no URL fragment'
+ 'Skip link: Target link (`href="/components/skip-link/preview"`) has no hash fragment'
}
})
})