Repository: HiDeoo/starlight-links-validator (ts)

Issue:
New configuration option to ignore anchor links?
### Is your feature request related to a problem?

Hey hey, love all the stuff you've built (we're just getting used to it on [developers.cloudflare.com](https://developers.cloudflare.com)).

As part of our previous build workflow (in Hugo), we had the following split between evaluating internal links:

- Every commit -> Broken internal links (and check for build + infinite redirects). This check was required.
- Every weekend -> Maintenance check for broken anchor links.

Essentially, it just wasn't worth it to fail the build for anchor links b/c we have a metric ton of content + we didn't want to inconvenience contributors.

### Describe the solution you'd like

Would it be possible to add a new [configuration option](https://starlight-links-validator.vercel.app/configuration/) where the link checker ignores the anchor part of links?

I'd love to be able to tap into the logic you've already built... but be able to tailor it a bit more specifically.

### Describe alternatives you've considered

I've thought about writing something from scratch, but it seems like this is very close to our use case.

### Additional Context

_No response_

Pull request description:
Add `errorOnInvalidHashes` option
Closes #57 

Patch:
diff --git a/docs/src/content/docs/configuration.md b/docs/src/content/docs/configuration.md
index 9b10fae..f97b527 100644
--- a/docs/src/content/docs/configuration.md
+++ b/docs/src/content/docs/configuration.md
@@ -101,6 +101,30 @@ export default defineConfig({
 })
 ```
 
+### `errorOnInvalidHashes`
+
+**Type:** `boolean`  
+**Default:** `true`
+
+By default, the Starlight Links Validator plugin will error if an internal link points to an [hash](https://developer.mozilla.org/en-US/docs/Web/API/URL/hash) that does not exist in the target page.
+If you want to only validate that pages exist but ignore hashes, you can set this option to `false`.
+
+This option should be used with caution but can be useful in large documentation with many contributors where hashes always being up-to-date can be difficult to maintain and validated on a different schedule, e.g. once a week.
+
+```js {6}
+export default defineConfig({
+  integrations: [
+    starlight({
+      plugins: [
+        starlightLinksValidator({
+          errorOnInvalidHashes: false,
+        }),
+      ],
+    }),
+  ],
+})
+```
+
 ### `exclude`
 
 **Type:** `string[]`  
diff --git a/docs/src/content/docs/getting-started.mdx b/docs/src/content/docs/getting-started.mdx
index 88071d4..37b90c6 100644
--- a/docs/src/content/docs/getting-started.mdx
+++ b/docs/src/content/docs/getting-started.mdx
@@ -5,8 +5,8 @@ title: Getting Started
 A [Starlight](https://starlight.astro.build) plugin to validate **_internal_** links in Markdown and MDX files.
 
 - Validate internal links to other pages
-- Validate internal links to anchors in other pages
-- Validate internal links to anchors in the same page
+- Validate internal links to hashes in other pages
+- Validate internal links to hashes in the same page
 - Ignore external links
 - Run only during a production build
 
diff --git a/packages/starlight-links-validator/README.md b/packages/starlight-links-validator/README.md
index f61dd63..db11b44 100644
--- a/packages/starlight-links-validator/README.md
+++ b/packages/starlight-links-validator/README.md
@@ -27,8 +27,8 @@ Want to get started immediately? Check out the [getting started guide](https://s
 A [Starlight](https://starlight.astro.build) plugin to validate **_internal_** links in Markdown and MDX files.
 
 - Validate internal links to other pages
-- Validate internal links to anchors in other pages
-- Validate internal links to anchors in the same page
+- Validate internal links to hashes in other pages
+- Validate internal links to hashes in the same page
 - Ignore external links
 - Run only during a production build
 
diff --git a/packages/starlight-links-validator/index.ts b/packages/starlight-links-validator/index.ts
index 2c9480f..0298fa1 100644
--- a/packages/starlight-links-validator/index.ts
+++ b/packages/starlight-links-validator/index.ts
@@ -33,6 +33,14 @@ const starlightLinksValidatorOptionsSchema = z
      * @default true
      */
     errorOnRelativeLinks: z.boolean().default(true),
+    /**
+     * Defines whether the plugin should error on invalid hashes.
+     *
+     * When set to `false`, the plugin will only validate link pages and ignore hashes.
+     *
+     * @default true
+     */
+    errorOnInvalidHashes: z.boolean().default(true),
     /**
      * Defines a list of links or glob patterns that should be excluded from validation.
      *
diff --git a/packages/starlight-links-validator/libs/validation.ts b/packages/starlight-links-validator/libs/validation.ts
index a1754ab..cbc08bf 100644
--- a/packages/starlight-links-validator/libs/validation.ts
+++ b/packages/starlight-links-validator/libs/validation.ts
@@ -15,7 +15,7 @@ import { getValidationData, type Headings } from './remark'
 
 export const ValidationErrorType = {
   InconsistentLocale: 'inconsistent locale',
-  InvalidAnchor: 'invalid anchor',
+  InvalidHash: 'invalid hash',
   InvalidLink: 'invalid link',
   RelativeLink: 'relative link',
   TrailingSlash: 'trailing slash',
@@ -59,7 +59,9 @@ export function validateLinks(
       }
 
       if (link.startsWith('#')) {
-        validateSelfAnchor(validationContext)
+        if (options.errorOnInvalidHashes) {
+          validateSelfHash(validationContext)
+        }
       } else {
         validateLink(validationContext)
       }
@@ -151,7 +153,9 @@ function validateLink(context: ValidationContext) {
   }
 
   if (hash && !fileHeadings.includes(hash)) {
-    addError(errors, filePath, link, ValidationErrorType.InvalidAnchor)
+    if (options.errorOnInvalidHashes) {
+      addError(errors, filePath, link, ValidationErrorType.InvalidHash)
+    }
     return
   }
 
@@ -176,9 +180,9 @@ function getFileHeadings(path: string, { headings, localeConfig, options }: Vali
 }
 
 /**
- * Validate a link to an anchor in the same page.
+ * Validate a link to an hash in the same page.
  */
-function validateSelfAnchor({ errors, link, filePath, headings }: ValidationContext) {
+function validateSelfHash({ errors, link, filePath, headings }: ValidationContext) {
   const sanitizedHash = link.replace(/^#/, '')
   const fileHeadings = headings.get(filePath)
 
@@ -187,7 +191,7 @@ function validateSelfAnchor({ errors, link, filePath, headings }: ValidationCont
   }
 
   if (!fileHeadings.includes(sanitizedHash)) {
-    addError(errors, filePath, link, 'invalid anchor')
+    addError(errors, filePath, link, ValidationErrorType.InvalidHash)
   }
 }
 


Test patch:
diff --git a/packages/starlight-links-validator/tests/base-path.test.ts b/packages/starlight-links-validator/tests/base-path.test.ts
index d37059d..b766117 100644
--- a/packages/starlight-links-validator/tests/base-path.test.ts
+++ b/packages/starlight-links-validator/tests/base-path.test.ts
@@ -19,8 +19,8 @@ test('should validate links when the `base` Astro option is set', async () => {
       ['/guides/example/#description', ValidationErrorType.InvalidLink],
       ['/unknown', ValidationErrorType.InvalidLink],
       ['/unknown/', ValidationErrorType.InvalidLink],
-      ['/test/guides/example#unknown', ValidationErrorType.InvalidAnchor],
-      ['/test/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+      ['/test/guides/example#unknown', ValidationErrorType.InvalidHash],
+      ['/test/guides/example/#unknown', ValidationErrorType.InvalidHash],
       ['/favicon.svg', ValidationErrorType.InvalidLink],
       ['/guidelines/dummy.pdf', ValidationErrorType.InvalidLink],
     ])
diff --git a/packages/starlight-links-validator/tests/basics.test.ts b/packages/starlight-links-validator/tests/basics.test.ts
index 177c2ff..6c8cbba 100644
--- a/packages/starlight-links-validator/tests/basics.test.ts
+++ b/packages/starlight-links-validator/tests/basics.test.ts
@@ -27,35 +27,35 @@ test('should not build with invalid links', async () => {
       ['/unknown/', ValidationErrorType.InvalidLink],
       ['/unknown#title', ValidationErrorType.InvalidLink],
       ['/unknown/#title', ValidationErrorType.InvalidLink],
-      ['#links', ValidationErrorType.InvalidAnchor],
-      ['/guides/example/#links', ValidationErrorType.InvalidAnchor],
+      ['#links', ValidationErrorType.InvalidHash],
+      ['/guides/example/#links', ValidationErrorType.InvalidHash],
       ['/icon.svg', ValidationErrorType.InvalidLink],
       ['/guidelines/ui.pdf', ValidationErrorType.InvalidLink],
       ['/unknown-ref', ValidationErrorType.InvalidLink],
-      ['#unknown-ref', ValidationErrorType.InvalidAnchor],
-      ['#anotherDiv', ValidationErrorType.InvalidAnchor],
+      ['#unknown-ref', ValidationErrorType.InvalidHash],
+      ['#anotherDiv', ValidationErrorType.InvalidHash],
       ['/guides/page-with-custom-slug', ValidationErrorType.InvalidLink],
       ['/release/@pkg/v0.2.0', ValidationErrorType.InvalidLink],
     ])
 
     expectValidationErrors(error, 'guides/example/', [
-      ['#links', ValidationErrorType.InvalidAnchor],
+      ['#links', ValidationErrorType.InvalidHash],
       ['/unknown/#links', ValidationErrorType.InvalidLink],
       ['/unknown', ValidationErrorType.InvalidLink],
-      ['#anotherBlock', ValidationErrorType.InvalidAnchor],
+      ['#anotherBlock', ValidationErrorType.InvalidHash],
       ['/icon.svg', ValidationErrorType.InvalidLink],
       ['/guidelines/ui.pdf', ValidationErrorType.InvalidLink],
       ['/linkcard/', ValidationErrorType.InvalidLink],
       ['/linkcard/#links', ValidationErrorType.InvalidLink],
-      ['#linkcard', ValidationErrorType.InvalidAnchor],
+      ['#linkcard', ValidationErrorType.InvalidHash],
       ['/linkbutton/', ValidationErrorType.InvalidLink],
       ['/linkbutton/#links', ValidationErrorType.InvalidLink],
-      ['#linkbutton', ValidationErrorType.InvalidAnchor],
+      ['#linkbutton', ValidationErrorType.InvalidHash],
     ])
 
     expectValidationErrors(error, 'guides/namespacetest/', [
-      ['#some-other-content', ValidationErrorType.InvalidAnchor],
-      ['/guides/namespacetest/#another-content', ValidationErrorType.InvalidAnchor],
+      ['#some-other-content', ValidationErrorType.InvalidHash],
+      ['/guides/namespacetest/#another-content', ValidationErrorType.InvalidHash],
     ])
 
     expectValidationErrors(error, 'relative/', [
diff --git a/packages/starlight-links-validator/tests/custom-ids.test.ts b/packages/starlight-links-validator/tests/custom-ids.test.ts
index 9fb6f74..7578879 100644
--- a/packages/starlight-links-validator/tests/custom-ids.test.ts
+++ b/packages/starlight-links-validator/tests/custom-ids.test.ts
@@ -12,6 +12,6 @@ test('should validate links with custom IDs', async () => {
   } catch (error) {
     expectValidationErrorCount(error, 1, 1)
 
-    expectValidationErrors(error, 'test/', [['#heading-with-custom-id', ValidationErrorType.InvalidAnchor]])
+    expectValidationErrors(error, 'test/', [['#heading-with-custom-id', ValidationErrorType.InvalidHash]])
   }
 })
diff --git a/packages/starlight-links-validator/tests/fallback.test.ts b/packages/starlight-links-validator/tests/fallback.test.ts
index 0f5b90d..686ffdb 100644
--- a/packages/starlight-links-validator/tests/fallback.test.ts
+++ b/packages/starlight-links-validator/tests/fallback.test.ts
@@ -36,8 +36,8 @@ test('should not build with invalid fallback links', async () => {
     expectValidationErrors(error, 'en/', [
       ['/en/guides/unknown', ValidationErrorType.InvalidLink],
       ['/en/guides/unknown/', ValidationErrorType.InvalidLink],
-      ['/en/guides/example#unknown', ValidationErrorType.InvalidAnchor],
-      ['/en/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+      ['/en/guides/example#unknown', ValidationErrorType.InvalidHash],
+      ['/en/guides/example/#unknown', ValidationErrorType.InvalidHash],
       ['/es/guides/example', ValidationErrorType.InvalidLink],
       ['/es/guides/example/', ValidationErrorType.InvalidLink],
     ])
@@ -45,8 +45,8 @@ test('should not build with invalid fallback links', async () => {
     expectValidationErrors(error, 'fr/', [
       ['/fr/guides/unknown', ValidationErrorType.InvalidLink],
       ['/fr/guides/unknown/', ValidationErrorType.InvalidLink],
-      ['/fr/guides/example#unknown', ValidationErrorType.InvalidAnchor],
-      ['/fr/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+      ['/fr/guides/example#unknown', ValidationErrorType.InvalidHash],
+      ['/fr/guides/example/#unknown', ValidationErrorType.InvalidHash],
     ])
 
     expectValidationErrors(error, 'fr/guides/test/', [['/', ValidationErrorType.InvalidLink]])
@@ -68,8 +68,8 @@ test('should not build with a root locale and invalid fallback links', async ()
     expectValidationErrors(error, '/', [
       ['/guides/unknown', ValidationErrorType.InvalidLink],
       ['/guides/unknown/', ValidationErrorType.InvalidLink],
-      ['/guides/example#unknown', ValidationErrorType.InvalidAnchor],
-      ['/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+      ['/guides/example#unknown', ValidationErrorType.InvalidHash],
+      ['/guides/example/#unknown', ValidationErrorType.InvalidHash],
       ['/es/guides/example', ValidationErrorType.InvalidLink],
       ['/es/guides/example/', ValidationErrorType.InvalidLink],
     ])
@@ -77,8 +77,8 @@ test('should not build with a root locale and invalid fallback links', async ()
     expectValidationErrors(error, 'fr/', [
       ['/fr/guides/unknown', ValidationErrorType.InvalidLink],
       ['/fr/guides/unknown/', ValidationErrorType.InvalidLink],
-      ['/fr/guides/example#unknown', ValidationErrorType.InvalidAnchor],
-      ['/fr/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+      ['/fr/guides/example#unknown', ValidationErrorType.InvalidHash],
+      ['/fr/guides/example/#unknown', ValidationErrorType.InvalidHash],
     ])
 
     expectValidationErrors(error, 'guides/test/', [['/en', ValidationErrorType.InvalidLink]])
diff --git a/packages/starlight-links-validator/tests/fixtures/basics-invalid-links/src/content/docs/guides/example.mdx b/packages/starlight-links-validator/tests/fixtures/basics-invalid-links/src/content/docs/guides/example.mdx
index 5cff26e..972f2b1 100644
--- a/packages/starlight-links-validator/tests/fixtures/basics-invalid-links/src/content/docs/guides/example.mdx
+++ b/packages/starlight-links-validator/tests/fixtures/basics-invalid-links/src/content/docs/guides/example.mdx
@@ -13,8 +13,8 @@ import { Card, CardGrid, LinkCard, LinkButton } from '@astrojs/starlight/compone
 
 ## Some links
 
-- [Link to invalid anchor in the same page](#links)
-- [Link to invalid anchor in another page](/unknown/#links)
+- [Link to invalid hash in the same page](#links)
+- [Link to invalid hash in another page](/unknown/#links)
 
 <a href="/unknown">HTML link to unknown page</a>
 
@@ -37,10 +37,10 @@ some content
 
 <CardGrid>
   <LinkCard title="LinkCard: unknown page" href="/linkcard/" />
-  <LinkCard title="LinkCard: unknown page and anchor" href="/linkcard/#links" />
-  <LinkCard title="LinkCard: unknown anchor" href="#linkcard" />
+  <LinkCard title="LinkCard: unknown page and hash" href="/linkcard/#links" />
+  <LinkCard title="LinkCard: unknown hash" href="#linkcard" />
 </CardGrid>
 
 <LinkButton href="/linkbutton/">LinkButton: unknown page</LinkButton>
-<LinkButton href="/linkbutton/#links">LinkButton: unknown page and anchor</LinkButton>
-<LinkButton href="#linkbutton">LinkButton: unknown anchor</LinkButton>
+<LinkButton href="/linkbutton/#links">LinkButton: unknown page and hash</LinkButton>
+<LinkButton href="#linkbutton">LinkButton: unknown hash</LinkButton>
diff --git a/packages/starlight-links-validator/tests/fixtures/basics-invalid-links/src/content/docs/test.md b/packages/starlight-links-validator/tests/fixtures/basics-invalid-links/src/content/docs/test.md
index 6c63e25..5150ea5 100644
--- a/packages/starlight-links-validator/tests/fixtures/basics-invalid-links/src/content/docs/test.md
+++ b/packages/starlight-links-validator/tests/fixtures/basics-invalid-links/src/content/docs/test.md
@@ -18,20 +18,20 @@ title: Test
 
 # More links
 
-- [Link to valid anchor in this page](#some-links)
-- [Link to invalid anchor in this page](#links)
-- [Link to valid anchor in another MDX page](/guides/example/#some-links)
-- [Link to invalid anchor in another MDX page](/guides/example/#links)
+- [Link to valid hash in this page](#some-links)
+- [Link to invalid hash in this page](#links)
+- [Link to valid hash in another MDX page](/guides/example/#some-links)
+- [Link to invalid hash in another MDX page](/guides/example/#links)
 - [Link to invalid asset](/icon.svg)
 - [Link to another invalid asset](/guidelines/ui.pdf)
 
 ## Links with references
 
 - [Link reference to unknwon page][ref-unknown-page]
-- [Link reference to invalid anchor][ref-invalid-anchor]
+- [Link reference to invalid hash][ref-invalid-hash]
 
 [ref-unknown-page]: /unknown-ref
-[ref-invalid-anchor]: #unknown-ref
+[ref-invalid-hash]: #unknown-ref
 
 <div id="aDiv">
 some content
diff --git a/packages/starlight-links-validator/tests/fixtures/basics-valid-links/src/content/docs/guides/example.mdx b/packages/starlight-links-validator/tests/fixtures/basics-valid-links/src/content/docs/guides/example.mdx
index 861927c..9e670f9 100644
--- a/packages/starlight-links-validator/tests/fixtures/basics-valid-links/src/content/docs/guides/example.mdx
+++ b/packages/starlight-links-validator/tests/fixtures/basics-valid-links/src/content/docs/guides/example.mdx
@@ -13,7 +13,7 @@ import { Card, CardGrid, LinkCard, LinkButton } from '@astrojs/starlight/compone
 
 ## Some links
 
-- [Link to anchor in the same page](#some-links)
+- [Link to hash in the same page](#some-links)
 
 <a href="/test">HTML link to another page</a>
 
@@ -40,10 +40,10 @@ some content
 
 <CardGrid>
   <LinkCard title="LinkCard: page" href="/test/" />
-  <LinkCard title="LinkCard: page and anchor" href="/test/#title" />
-  <LinkCard title="LinkCard: anchor" href="#steps" />
+  <LinkCard title="LinkCard: page and hash" href="/test/#title" />
+  <LinkCard title="LinkCard: hash" href="#steps" />
 </CardGrid>
 
 <LinkButton href="/test/">LinkCard: unknown page</LinkButton>
-<LinkButton href="/test/#title">LinkCard: unknown page and anchor</LinkButton>
-<LinkButton href="#steps">LinkCard: unknown anchor</LinkButton>
+<LinkButton href="/test/#title">LinkCard: unknown page and hash</LinkButton>
+<LinkButton href="#steps">LinkCard: unknown hash</LinkButton>
diff --git a/packages/starlight-links-validator/tests/fixtures/basics-valid-links/src/content/docs/index.md b/packages/starlight-links-validator/tests/fixtures/basics-valid-links/src/content/docs/index.md
index 96cc519..06af854 100644
--- a/packages/starlight-links-validator/tests/fixtures/basics-valid-links/src/content/docs/index.md
+++ b/packages/starlight-links-validator/tests/fixtures/basics-valid-links/src/content/docs/index.md
@@ -21,25 +21,25 @@ title: Index
 
 # More links
 
-- [Link to anchor in this page](#some-links)
-- [Link to anchor in another MDX page](/guides/example/#some-links)
+- [Link to hash in this page](#some-links)
+- [Link to hash in another MDX page](/guides/example/#some-links)
 - [Link to an asset](/favicon.svg)
 - [Link to another asset](/guidelines/dummy.pdf)
 
 ## A more `complex` heading
 
-- [Link to more complex anchor](#a-more-complex-heading)
+- [Link to more complex hash](#a-more-complex-heading)
 
 ## Links with references
 
 - [ref]
 - [Link reference][ref]
-- [Link reference with anchor in this page][ref-with-anchor-internal]
-- [Link reference with anchor in another page][ref-with-anchor-external]
+- [Link reference with hash in this page][ref-with-hash-internal]
+- [Link reference with hash in another page][ref-with-hash-external]
 
 [ref]: /test
-[ref-with-anchor-internal]: #some-links
-[ref-with-anchor-external]: /test#title
+[ref-with-hash-internal]: #some-links
+[ref-with-hash-external]: /test#title
 
 ## Link to page with custom slug
 
diff --git a/packages/starlight-links-validator/tests/fixtures/invalid-hashes-invalid-links/astro.config.ts b/packages/starlight-links-validator/tests/fixtures/invalid-hashes-invalid-links/astro.config.ts
new file mode 100644
index 0000000..3ffc24f
--- /dev/null
+++ b/packages/starlight-links-validator/tests/fixtures/invalid-hashes-invalid-links/astro.config.ts
@@ -0,0 +1,13 @@
+import starlight from '@astrojs/starlight'
+import { defineConfig } from 'astro/config'
+
+import starlightLinksValidator from '../..'
+
+export default defineConfig({
+  integrations: [
+    starlight({
+      plugins: [starlightLinksValidator({ errorOnInvalidHashes: false })],
+      title: 'Starlight Links Validator Tests - invalid hashes invalid links',
+    }),
+  ],
+})
diff --git a/packages/starlight-links-validator/tests/fixtures/invalid-hashes-invalid-links/src/content/docs/guides/example.mdx b/packages/starlight-links-validator/tests/fixtures/invalid-hashes-invalid-links/src/content/docs/guides/example.mdx
new file mode 100644
index 0000000..972f2b1
--- /dev/null
+++ b/packages/starlight-links-validator/tests/fixtures/invalid-hashes-invalid-links/src/content/docs/guides/example.mdx
@@ -0,0 +1,46 @@
+---
+title: Example
+---
+
+import { Card, CardGrid, LinkCard, LinkButton } from '@astrojs/starlight/components'
+
+## Steps
+
+<CardGrid stagger>
+  <Card title="Step 1">Do something</Card>
+  <Card title="Step 2">Do something else</Card>
+</CardGrid>
+
+## Some links
+
+- [Link to invalid hash in the same page](#links)
+- [Link to invalid hash in another page](/unknown/#links)
+
+<a href="/unknown">HTML link to unknown page</a>
+
+<div id="aBlock">
+some content
+
+some content
+
+some content
+
+some content
+
+  <a href="#anotherBlock">
+    test
+  </a>
+</div>
+
+<a href="/icon.svg">Link to invalid asset</a>
+<a href="/guidelines/ui.pdf">Link to another invalid asset</a>
+
+<CardGrid>
+  <LinkCard title="LinkCard: unknown page" href="/linkcard/" />
+  <LinkCard title="LinkCard: unknown page and hash" href="/linkcard/#links" />
+  <LinkCard title="LinkCard: unknown hash" href="#linkcard" />
+</CardGrid>
+
+<LinkButton href="/linkbutton/">LinkButton: unknown page</LinkButton>
+<LinkButton href="/linkbutton/#links">LinkButton: unknown page and hash</LinkButton>
+<LinkButton href="#linkbutton">LinkButton: unknown hash</LinkButton>
diff --git a/packages/starlight-links-validator/tests/fixtures/invalid-hashes-invalid-links/src/content/docs/test.md b/packages/starlight-links-validator/tests/fixtures/invalid-hashes-invalid-links/src/content/docs/test.md
new file mode 100644
index 0000000..c01041e
--- /dev/null
+++ b/packages/starlight-links-validator/tests/fixtures/invalid-hashes-invalid-links/src/content/docs/test.md
@@ -0,0 +1,48 @@
+---
+title: Test
+---
+
+# Some links
+
+- [External link](https://starlight.astro.build/)
+
+- [External link prefixed with a slash](/https://starlight.astro.build/)
+
+- [Home page](/)
+
+- [Unknown page](/unknown)
+- [Unknown page](/unknown/)
+
+- [Unknown page with hash](/unknown#title)
+- [Unknown page with hash](/unknown/#title)
+
+# More links
+
+- [Link to valid hash in this page](#some-links)
+- [Link to invalid hash in this page](#links)
+- [Link to valid hash in another MDX page](/guides/example/#some-links)
+- [Link to invalid hash in another MDX page](/guides/example/#links)
+- [Link to invalid asset](/icon.svg)
+- [Link to another invalid asset](/guidelines/ui.pdf)
+
+## Links with references
+
+- [Link reference to unknown page][ref-unknown-page]
+- [Link reference to invalid hash][ref-invalid-hash]
+
+[ref-unknown-page]: /unknown-ref
+[ref-invalid-hash]: #unknown-ref
+
+<div id="aDiv">
+some content
+
+some content
+
+some content
+
+some content
+
+  <a href="#anotherDiv">
+    test
+  </a>
+</div>
diff --git a/packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/astro.config.ts b/packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/astro.config.ts
new file mode 100644
index 0000000..3b15f9f
--- /dev/null
+++ b/packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/astro.config.ts
@@ -0,0 +1,13 @@
+import starlight from '@astrojs/starlight'
+import { defineConfig } from 'astro/config'
+
+import starlightLinksValidator from '../..'
+
+export default defineConfig({
+  integrations: [
+    starlight({
+      plugins: [starlightLinksValidator({ errorOnInvalidHashes: false })],
+      title: 'Starlight Links Validator Tests - invalid hashes valid links',
+    }),
+  ],
+})
diff --git a/packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/src/content/docs/guides/example.mdx b/packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/src/content/docs/guides/example.mdx
new file mode 100644
index 0000000..3dcc264
--- /dev/null
+++ b/packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/src/content/docs/guides/example.mdx
@@ -0,0 +1,47 @@
+---
+title: Example
+---
+
+import { Card, CardGrid, LinkCard, LinkButton } from '@astrojs/starlight/components'
+
+## Steps
+
+<CardGrid stagger>
+  <Card title="Step 1">Do something</Card>
+  <Card title="Step 2">Do something else</Card>
+</CardGrid>
+
+## Some links
+
+- [Link to valid hash in the same page](#some-links)
+- [Link to invalid hash in the same page](#unknown)
+
+<div id="aBlock">
+some content
+
+some content
+
+some <span id="aText">other</span> content
+
+some content
+
+<a href="#unknownBlock">unknown block</a>
+
+some content
+
+  <a href="#unknownText">
+    test text
+  </a>
+</div>
+
+<CardGrid>
+  <LinkCard title="LinkCard: page and valid hash" href="/test/#title" />
+  <LinkCard title="LinkCard: page and invalid hash" href="/test/#unknown" />
+  <LinkCard title="LinkCard: valid hash" href="#steps" />
+  <LinkCard title="LinkCard: invalid hash" href="#unknown" />
+</CardGrid>
+
+<LinkButton href="/test/#title">LinkCard: unknown page and valid hash</LinkButton>
+<LinkButton href="/test/#title">LinkCard: unknown page and invalid hash</LinkButton>
+<LinkButton href="#steps">LinkCard: valid hash</LinkButton>
+<LinkButton href="#unknown">LinkCard: invalid hash</LinkButton>
diff --git a/packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/src/content/docs/index.md b/packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/src/content/docs/index.md
new file mode 100644
index 0000000..0ec4cd7
--- /dev/null
+++ b/packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/src/content/docs/index.md
@@ -0,0 +1,38 @@
+---
+title: Index
+---
+
+# Some links
+
+- [External link](https://starlight.astro.build/)
+
+- [Home page](/)
+
+- [Test page](/test)
+- [Test page](/test/)
+
+- [Test page with valid hash](/test#title)
+- [Test page with valid hash](/test/#title)
+
+- [Test page with invalid hash](/test#unknown)
+- [Test page with invalid hash](/test/#unknown)
+
+# More links
+
+- [Link to valid hash in this page](#some-links)
+- [Link to invalid hash in this page](#unknown)
+- [Link to valid hash in another MDX page](/guides/example/#some-links)
+- [Link to invalid hash in another MDX page](/guides/example/#unknown)
+
+## Links with references
+
+- [Link reference with valid hash in this page][ref-with-valid-hash-internal]
+- [Link reference with valid hash in another page][ref-with-valid-hash-external]
+
+- [Link reference with invalid hash in this page][ref-with-invalid-hash-internal]
+- [Link reference with invalid hash in another page][ref-with-invalid-hash-external]
+
+[ref-with-valid-hash-internal]: #some-links
+[ref-with-valid-hash-external]: /test#title
+[ref-with-invalid-hash-internal]: #unknown
+[ref-with-invalid-hash-external]: /test#unknown
diff --git a/packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/src/content/docs/test.md b/packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/src/content/docs/test.md
new file mode 100644
index 0000000..1bba7cc
--- /dev/null
+++ b/packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/src/content/docs/test.md
@@ -0,0 +1,27 @@
+---
+title: Test
+---
+
+## Title
+
+Some content.
+
+- [Link to same page](/test)
+
+<div>
+some content
+
+some content
+
+some content
+
+some content
+
+  <a href="#aDiv">
+    test
+  </a>
+</div>
+
+## Title
+
+More content.
diff --git a/packages/starlight-links-validator/tests/invalid-hashes.test.ts b/packages/starlight-links-validator/tests/invalid-hashes.test.ts
new file mode 100644
index 0000000..8f5a11f
--- /dev/null
+++ b/packages/starlight-links-validator/tests/invalid-hashes.test.ts
@@ -0,0 +1,42 @@
+import { expect, test } from 'vitest'
+
+import { ValidationErrorType } from '../libs/validation'
+
+import { expectValidationErrorCount, expectValidationErrors, loadFixture } from './utils'
+
+test('should build with invalid hashes', async () => {
+  await expect(loadFixture('invalid-hashes-valid-links')).resolves.not.toThrow()
+})
+
+test('should not build with invalid links but ignore invalid hashes', async () => {
+  expect.assertions(3)
+
+  try {
+    await loadFixture('invalid-hashes-invalid-links')
+  } catch (error) {
+    expectValidationErrorCount(error, 17, 2)
+
+    expectValidationErrors(error, 'test/', [
+      ['/https://starlight.astro.build/', ValidationErrorType.InvalidLink],
+      ['/', ValidationErrorType.InvalidLink],
+      ['/unknown', ValidationErrorType.InvalidLink],
+      ['/unknown/', ValidationErrorType.InvalidLink],
+      ['/unknown#title', ValidationErrorType.InvalidLink],
+      ['/unknown/#title', ValidationErrorType.InvalidLink],
+      ['/icon.svg', ValidationErrorType.InvalidLink],
+      ['/guidelines/ui.pdf', ValidationErrorType.InvalidLink],
+      ['/unknown-ref', ValidationErrorType.InvalidLink],
+    ])
+
+    expectValidationErrors(error, 'guides/example/', [
+      ['/unknown/#links', ValidationErrorType.InvalidLink],
+      ['/unknown', ValidationErrorType.InvalidLink],
+      ['/icon.svg', ValidationErrorType.InvalidLink],
+      ['/guidelines/ui.pdf', ValidationErrorType.InvalidLink],
+      ['/linkcard/', ValidationErrorType.InvalidLink],
+      ['/linkcard/#links', ValidationErrorType.InvalidLink],
+      ['/linkbutton/', ValidationErrorType.InvalidLink],
+      ['/linkbutton/#links', ValidationErrorType.InvalidLink],
+    ])
+  }
+})
diff --git a/packages/starlight-links-validator/tests/relative.test.ts b/packages/starlight-links-validator/tests/relative.test.ts
index 871d381..6b7f173 100644
--- a/packages/starlight-links-validator/tests/relative.test.ts
+++ b/packages/starlight-links-validator/tests/relative.test.ts
@@ -15,8 +15,8 @@ test('should ignore relative links when the `errorOnRelativeLinks` option is set
     expectValidationErrors(error, 'test/', [
       ['/unknown', ValidationErrorType.InvalidLink],
       ['/unknown/', ValidationErrorType.InvalidLink],
-      ['/guides/example#unknown', ValidationErrorType.InvalidAnchor],
-      ['/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+      ['/guides/example#unknown', ValidationErrorType.InvalidHash],
+      ['/guides/example/#unknown', ValidationErrorType.InvalidHash],
     ])
   }
 })
diff --git a/packages/starlight-links-validator/tests/src-dir.test.ts b/packages/starlight-links-validator/tests/src-dir.test.ts
index a00d240..a7fafcf 100644
--- a/packages/starlight-links-validator/tests/src-dir.test.ts
+++ b/packages/starlight-links-validator/tests/src-dir.test.ts
@@ -15,8 +15,8 @@ test('should validate links when the `srcDir` Astro option is set', async () =>
     expectValidationErrors(error, 'test/', [
       ['/unknown', ValidationErrorType.InvalidLink],
       ['/unknown/', ValidationErrorType.InvalidLink],
-      ['/guides/example#unknown', ValidationErrorType.InvalidAnchor],
-      ['/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+      ['/guides/example#unknown', ValidationErrorType.InvalidHash],
+      ['/guides/example/#unknown', ValidationErrorType.InvalidHash],
     ])
   }
 })
diff --git a/packages/starlight-links-validator/tests/trailing.test.ts b/packages/starlight-links-validator/tests/trailing.test.ts
index 9c343c5..f3e4b90 100644
--- a/packages/starlight-links-validator/tests/trailing.test.ts
+++ b/packages/starlight-links-validator/tests/trailing.test.ts
@@ -17,8 +17,8 @@ test('should validate links when the `trailingSlash` Astro option is set to `nev
       ['/guides/example/#description', ValidationErrorType.TrailingSlash],
       ['/unknown', ValidationErrorType.InvalidLink],
       ['/unknown/', ValidationErrorType.InvalidLink],
-      ['/guides/example#unknown', ValidationErrorType.InvalidAnchor],
-      ['/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+      ['/guides/example#unknown', ValidationErrorType.InvalidHash],
+      ['/guides/example/#unknown', ValidationErrorType.InvalidHash],
     ])
   }
 })
@@ -36,8 +36,8 @@ test('should validate links when the `trailingSlash` Astro option is set to `alw
       ['/guides/example#description', ValidationErrorType.TrailingSlash],
       ['/unknown', ValidationErrorType.InvalidLink],
       ['/unknown/', ValidationErrorType.InvalidLink],
-      ['/guides/example#unknown', ValidationErrorType.InvalidAnchor],
-      ['/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+      ['/guides/example#unknown', ValidationErrorType.InvalidHash],
+      ['/guides/example/#unknown', ValidationErrorType.InvalidHash],
     ])
   }
 })