diff --git a/.eleventy.js b/.eleventy.js
index 7b238c15..795904cd 100644
--- a/.eleventy.js
+++ b/.eleventy.js
@@ -64,6 +64,9 @@ export default async function (eleventyConfig) {
   eleventyConfig.addPassthroughCopy({
     'node_modules/@cdransf/theme-toggle/theme-toggle.js': 'assets/scripts/components/theme-toggle.js',
   })
+  eleventyConfig.addPassthroughCopy({
+    'node_modules/@cdransf/select-pagination/select-pagination.js': 'assets/scripts/components/select-pagination.js',
+  })
   eleventyConfig.addPassthroughCopy({
     'node_modules/@zachleat/webcare-webshare/webcare-webshare.js': 'assets/scripts/components/webcare-webshare.js'
   })
diff --git a/package-lock.json b/package-lock.json
index f70467cd..97874e99 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,21 +1,21 @@
 {
   "name": "coryd.dev",
-  "version": "9.5.1",
+  "version": "9.6.4",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "coryd.dev",
-      "version": "9.5.1",
+      "version": "9.6.4",
       "license": "MIT",
       "dependencies": {
         "@cdransf/api-text": "^1.2.2",
+        "@cdransf/select-pagination": "^1.0.2",
         "@cdransf/theme-toggle": "^1.2.3",
         "@daviddarnes/mastodon-post": "^1.1.1",
         "@remy/webmention": "^1.5.0",
         "@zachleat/webcare-webshare": "^1.0.3",
         "minisearch": "^6.3.0",
-        "terser": "^5.30.1",
         "youtube-video-element": "^1.0.0"
       },
       "devDependencies": {
@@ -40,6 +40,7 @@
         "markdown-it-anchor": "^8.4.1",
         "markdown-it-footnote": "^4.0.0",
         "slugify": "^1.6.6",
+        "terser": "^5.30.1",
         "writing-stats": "^1.0.6"
       }
     },
@@ -1219,6 +1220,11 @@
       "integrity": "sha512-hL94Eez7twe31eQzH4iNk6bb3HjRc2Qnqq+d1O9XpVhKnJPKkMlp+trqnidvos8EEqU4H3VkyVANachFm5cxSw==",
       "dev": true
     },
+    "node_modules/@cdransf/select-pagination": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@cdransf/select-pagination/-/select-pagination-1.0.2.tgz",
+      "integrity": "sha512-wDQ/jaBSZNf4bUB5NXC0G86IJlOEEyUBaOmRNOoMjmFhBW6RXVXhAZr6ggBksbSi7guImZgdW556CxeGiJjt7w=="
+    },
     "node_modules/@cdransf/theme-toggle": {
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/@cdransf/theme-toggle/-/theme-toggle-1.2.3.tgz",
@@ -1699,6 +1705,7 @@
       "version": "0.3.5",
       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
       "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+      "dev": true,
       "dependencies": {
         "@jridgewell/set-array": "^1.2.1",
         "@jridgewell/sourcemap-codec": "^1.4.10",
@@ -1712,6 +1719,7 @@
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
       "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "dev": true,
       "engines": {
         "node": ">=6.0.0"
       }
@@ -1720,6 +1728,7 @@
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
       "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+      "dev": true,
       "engines": {
         "node": ">=6.0.0"
       }
@@ -1728,6 +1737,7 @@
       "version": "0.3.6",
       "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
       "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
+      "dev": true,
       "dependencies": {
         "@jridgewell/gen-mapping": "^0.3.5",
         "@jridgewell/trace-mapping": "^0.3.25"
@@ -1736,12 +1746,14 @@
     "node_modules/@jridgewell/sourcemap-codec": {
       "version": "1.4.15",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
-      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
+      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
+      "dev": true
     },
     "node_modules/@jridgewell/trace-mapping": {
       "version": "0.3.25",
       "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
       "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+      "dev": true,
       "dependencies": {
         "@jridgewell/resolve-uri": "^3.1.0",
         "@jridgewell/sourcemap-codec": "^1.4.14"
@@ -2920,6 +2932,7 @@
       "version": "8.11.3",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
       "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+      "dev": true,
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -3178,7 +3191,8 @@
     "node_modules/buffer-from": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
-      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+      "dev": true
     },
     "node_modules/call-bind": {
       "version": "1.0.7",
@@ -6330,6 +6344,7 @@
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
       "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -6338,6 +6353,7 @@
       "version": "0.5.21",
       "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
       "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+      "dev": true,
       "dependencies": {
         "buffer-from": "^1.0.0",
         "source-map": "^0.6.0"
@@ -6427,6 +6443,7 @@
       "version": "5.30.3",
       "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz",
       "integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==",
+      "dev": true,
       "dependencies": {
         "@jridgewell/source-map": "^0.3.3",
         "acorn": "^8.8.2",
@@ -6443,7 +6460,8 @@
     "node_modules/terser/node_modules/commander": {
       "version": "2.20.3",
       "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "dev": true
     },
     "node_modules/timers-ext": {
       "version": "0.1.7",
diff --git a/package.json b/package.json
index 29144887..56a67464 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "coryd.dev",
-  "version": "9.6.4",
+  "version": "9.7.0",
   "description": "The source for my personal site. Built using 11ty.",
   "type": "module",
   "scripts": {
@@ -20,12 +20,12 @@
   "license": "MIT",
   "dependencies": {
     "@cdransf/api-text": "^1.2.2",
+    "@cdransf/select-pagination": "^1.0.2",
     "@cdransf/theme-toggle": "^1.2.3",
     "@daviddarnes/mastodon-post": "^1.1.1",
     "@remy/webmention": "^1.5.0",
     "@zachleat/webcare-webshare": "^1.0.3",
     "minisearch": "^6.3.0",
-    "terser": "^5.30.1",
     "youtube-video-element": "^1.0.0"
   },
   "devDependencies": {
@@ -50,6 +50,7 @@
     "markdown-it-anchor": "^8.4.1",
     "markdown-it-footnote": "^4.0.0",
     "slugify": "^1.6.6",
+    "terser": "^5.30.1",
     "writing-stats": "^1.0.6"
   }
 }
diff --git a/src/_includes/partials/paginator.liquid b/src/_includes/partials/paginator.liquid
index 53d1874c..f2e4e6ca 100644
--- a/src/_includes/partials/paginator.liquid
+++ b/src/_includes/partials/paginator.liquid
@@ -2,10 +2,7 @@
   {% render "../../assets/styles/components/paginator.css" %}
 {% endcapture %}
 <style>{{ css }}</style>
-{% capture js %}
-  {% render "../../assets/scripts/paginator.js" %}
-{% endcapture %}
-<script>{{ js }}</script>
+<script type="module" src="/assets/scripts/components/select-pagination.js"></script>
 <nav aria-label="Blog pagination" class="pagination flex--centered">
   {% if pagination.href.previous %}
     <a href="{{ pagination.href.previous }}" aria-label="Previous page">
@@ -19,18 +16,18 @@
       {% tablericon "arrow-left" "Prevous" %}
     </span>
   {% endif %}
-  <noscript>
-    <div class="text--centered">
-      <span aria-current="page">{{ pagination.pageNumber | plus: 1 }}</span> of {{ pagination.links.size }}
-    </div>
-  </noscript>
-  <div class="select client-side">
-    <select id="pagination" aria-label="Page selection">
+  <select-pagination>
+    <select aria-label="Page selection">
       {% for pageEntry in pagination.pages %}
       <option value="{{ forloop.index | minus: 1 }}">{{ forloop.index }} of {{ pagination.links.size }}</option>
       {% endfor %}
     </select>
-  </div>
+    <noscript>
+      <div class="text--centered">
+        <span aria-current="page">{{ pagination.pageNumber | plus: 1 }}</span> of {{ pagination.links.size }}
+      </div>
+    </noscript>
+  </select-pagination>
   {% if pagination.href.next %}
     <a href="{{ pagination.href.next }}" aria-label="Next page">
       {% tablericon "arrow-right" "Next" %}
diff --git a/src/assets/scripts/paginator.js b/src/assets/scripts/paginator.js
deleted file mode 100644
index f17f2cdf..00000000
--- a/src/assets/scripts/paginator.js
+++ /dev/null
@@ -1,25 +0,0 @@
-window.onload = () => {
-  const pagination = document.getElementById('pagination')
-  const uri = window.location.pathname
-  const urlSegments = uri.split('/').filter(segment => segment !== '')
-  let pageNumber = parseInt(urlSegments[urlSegments.length - 1]) || 0
-  pagination.querySelector(`option[value="${pageNumber.toString()}"]`).setAttribute('selected', 'selected')
-
-  if (pagination) {
-    pagination.addEventListener('change', (event) => {
-      pageNumber = event.target.value
-
-      if (urlSegments.length === 0 || isNaN(urlSegments[urlSegments.length - 1])) {
-        urlSegments.push(pageNumber.toString())
-      } else {
-        urlSegments[urlSegments.length - 1] = pageNumber.toString()
-      }
-
-      if (pageNumber === 0) {
-        window.location.href = `${window.location.protocol}//${window.location.host}/`
-      } else {
-        window.location = `${window.location.protocol}//${window.location.host}/${urlSegments.join('/')}`
-      }
-    })
-  }
-}
\ No newline at end of file
diff --git a/src/assets/styles/index.css b/src/assets/styles/index.css
index abcb205c..90572ed2 100644
--- a/src/assets/styles/index.css
+++ b/src/assets/styles/index.css
@@ -413,7 +413,8 @@ select {
   line-height: var(--line-height-base);
 }
 
-.select {
+.select,
+select-pagination {
   border-radius: var(--rounded-full);
   background-color: var(--accent-color);
   padding: 0 var(--sizing-lg);
@@ -426,17 +427,22 @@ select {
 }
 
 .select,
-.select select {
+.select select,
+select-pagination,
+select-pagination select {
   min-height: calc(var(--sizing-3xl) * 1.25);
   min-width:  calc(var(--sizing-3xl) * 4);;
 }
 
 .select select,
-.select::after {
+.select::after,
+select-pagination select,
+select-pagination::after {
   grid-area: select;
 }
 
-.select::after {
+.select::after,
+select-pagination::after {
   content: '';
   width: var(--sizing-md);
   height: var(--sizing-sm);
diff --git a/src/posts/2024/enhancing-pagination-with-a-page-selector.md b/src/posts/2024/enhancing-pagination-with-a-page-selector.md
index 31fdb5ad..e2e86ac9 100644
--- a/src/posts/2024/enhancing-pagination-with-a-page-selector.md
+++ b/src/posts/2024/enhancing-pagination-with-a-page-selector.md
@@ -40,4 +40,6 @@ We wait for the document to load, select the pagination DOM node, get the curren
 
   Within the `change` event listener I check whether we've extracted a url segment *and* that the last segment is a valid number — if not, we add a new numeric segment. If it is numeric, we replace it with the new page number. Finally, we have special handling for the root section — because my first page is at `/` and the second is at `/1/` we need to correctly navigate the user should the pageNumber be `0`.
 
-  With that, we have quicker and more convenient page navigation for users that have JavaScript enabled and a handy page count for users that have disabled JavaScript in their browser.
\ No newline at end of file
+  With that, we have quicker and more convenient page navigation for users that have JavaScript enabled and a handy page count for users that have disabled JavaScript in their browser.
+
+  {% render "partials/banners/npm.liquid", url: 'https://www.npmjs.com/package/@cdransf/select-pagination', command: 'npm i @cdransf/select-pagination' %}
\ No newline at end of file