Bootstrap 5 Pagination Styles
Six pagination styles: round pills, a joined segmented bar, outlined squares, chevron previous/next with progress dots, a numbered bar with ellipsis and a results count, and a load-more button with a progress bar.
<section class="wdw-stage">
<div class="container" style="max-width: 980px;">
<div class="mb-4">
<span class="wdw-eyebrow">Components</span>
<h1 class="wdw-heading h2 mt-1 mb-2">Pagination styles</h1>
<p class="wdw-muted mb-0">Six ways to page through results. Every example keeps its own state, so click around and use the keyboard too.</p>
</div>
<div class="wdw-card p-3 p-md-4">
<div class="row g-3">
<div class="col-md-6">
<div class="pg-cell h-100">
<div class="fw-semibold">Pill</div><div class="small wdw-muted mb-3">Round numbers, compact and friendly</div>
<nav aria-label="Pill pagination"><ul class="pagination pg pg-pill mb-0" data-pager data-total="6"></ul></nav>
</div>
</div>
<div class="col-md-6">
<div class="pg-cell h-100">
<div class="fw-semibold">Segmented</div><div class="small wdw-muted mb-3">One joined control with arrows</div>
<nav aria-label="Segmented pagination"><ul class="pagination pg pg-seg mb-0" data-pager data-total="5" data-arrows="1"></ul></nav>
</div>
</div>
<div class="col-md-6">
<div class="pg-cell h-100">
<div class="fw-semibold">Outlined squares</div><div class="small wdw-muted mb-3">Quiet borders, strong active state</div>
<nav aria-label="Square pagination"><ul class="pagination pg pg-sq mb-0" data-pager data-total="5" data-arrows="1"></ul></nav>
</div>
</div>
<div class="col-md-6">
<div class="pg-cell h-100">
<div class="fw-semibold">Previous / next</div><div class="small wdw-muted mb-3">Chevron buttons with progress dots</div>
<nav aria-label="Previous and next pagination" class="d-flex align-items-center gap-3" data-mini data-total="6">
<button type="button" class="btn pg-round" data-dir="-1" aria-label="Previous page"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 6-6 6 6 6"/></svg></button>
<div class="flex-grow-1 text-center">
<div class="pg-dots" data-dots aria-hidden="true"></div>
<div class="small fw-semibold mt-2" aria-live="polite" data-count></div>
</div>
<button type="button" class="btn pg-round pg-round-primary" data-dir="1" aria-label="Next page"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 6 6 6-6 6"/></svg></button>
</nav>
</div>
</div>
<div class="col-12">
<div class="pg-cell">
<div class="fw-semibold">Numbered with ellipsis</div><div class="small wdw-muted mb-3">Long result sets with a results count</div>
<div class="d-flex flex-column flex-lg-row align-items-lg-center justify-content-between gap-3">
<p class="small wdw-muted mb-0" id="pg-info" aria-live="polite">Showing <strong>1-10</strong> of <strong>248</strong> results</p>
<nav aria-label="Results pagination"><ul class="pagination pg pg-pill pg-dense mb-0" data-pager data-total="25" data-ellipsis="1" data-arrows="1" data-info="pg-info" data-items="248" data-per="10"></ul></nav>
</div>
</div>
</div>
<div class="col-12">
<div class="pg-cell" data-more data-total="24" data-step="8">
<div class="fw-semibold">Load more</div><div class="small wdw-muted mb-3">Append results instead of changing pages</div>
<div class="d-flex justify-content-between small mb-2"><span aria-live="polite" data-more-count></span><span class="wdw-muted" data-more-pct></span></div>
<div class="progress pg-progress" role="progressbar" aria-label="Results loaded" aria-valuemin="0" aria-valuemax="100"><div class="progress-bar" data-more-bar></div></div>
<button type="button" class="btn btn-outline-primary rounded-pill w-100 mt-3" data-more-btn>Load more results</button>
</div>
</div>
</div>
</div>
</div>
</section>
/* Web Designer Wall library style — shared base for every Bootstrap 5 snippet.
Indigo accent, Plus Jakarta Sans, soft cards, pill buttons. Works in light and dark. */
@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap");
:root,
[data-bs-theme="light"] {
--wdw-font: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--bs-body-font-family: var(--wdw-font);
--bs-primary: #4f46e5;
--bs-primary-rgb: 79, 70, 229;
--bs-primary-text-emphasis: #3730a3;
--bs-primary-bg-subtle: #eef2ff;
--bs-primary-border-subtle: #c7d2fe;
--bs-link-color: #4f46e5;
--bs-link-color-rgb: 79, 70, 229;
--bs-link-hover-color: #4338ca;
--bs-link-hover-color-rgb: 67, 56, 202;
--bs-focus-ring-color: rgba(79, 70, 229, 0.25);
--bs-border-radius: 0.75rem;
--bs-border-radius-lg: 1rem;
--bs-border-radius-xl: 1.25rem;
--wdw-radius-card: 1.25rem;
--wdw-shadow: 0 1px 2px rgba(16, 24, 40, 0.05), 0 16px 40px -16px rgba(16, 24, 40, 0.22);
--wdw-shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06), 0 4px 12px -4px rgba(16, 24, 40, 0.12);
--wdw-stage: #f5f6fb;
}
[data-bs-theme="dark"] {
--bs-primary: #818cf8;
--bs-primary-rgb: 129, 140, 248;
--bs-primary-text-emphasis: #c7d2fe;
--bs-primary-bg-subtle: rgba(129, 140, 248, 0.14);
--bs-primary-border-subtle: rgba(129, 140, 248, 0.35);
--bs-link-color: #a5b4fc;
--bs-link-color-rgb: 165, 180, 252;
--bs-link-hover-color: #c7d2fe;
--bs-link-hover-color-rgb: 199, 210, 254;
--bs-focus-ring-color: rgba(129, 140, 248, 0.35);
--wdw-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 16px 40px -16px rgba(0, 0, 0, 0.7);
--wdw-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 12px -4px rgba(0, 0, 0, 0.5);
--wdw-stage: #0f1117;
}
body { font-family: var(--wdw-font); -webkit-font-smoothing: antialiased; }
/* Bootstrap compiles its blue into these components; point them at the accent. */
.btn-primary {
--bs-btn-bg: var(--bs-primary); --bs-btn-border-color: var(--bs-primary);
--bs-btn-hover-bg: #4338ca; --bs-btn-hover-border-color: #4338ca;
--bs-btn-active-bg: #3730a3; --bs-btn-active-border-color: #3730a3;
--bs-btn-disabled-bg: var(--bs-primary); --bs-btn-disabled-border-color: var(--bs-primary);
--bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
}
[data-bs-theme="dark"] .btn-primary { --bs-btn-color: #0f1117; --bs-btn-hover-bg: #a5b4fc; --bs-btn-hover-border-color: #a5b4fc; --bs-btn-hover-color: #0f1117; --bs-btn-active-color: #0f1117; }
.btn-outline-primary {
--bs-btn-color: var(--bs-primary); --bs-btn-border-color: var(--bs-primary);
--bs-btn-hover-bg: var(--bs-primary); --bs-btn-hover-border-color: var(--bs-primary);
--bs-btn-active-bg: var(--bs-primary); --bs-btn-active-border-color: var(--bs-primary);
--bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
}
.btn { --bs-btn-font-weight: 600; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; gap: .5rem; }
.btn-sm { min-height: 36px; }
.form-control, .form-select { min-height: 44px; border-radius: .75rem; }
.form-control:focus, .form-select:focus, .form-check-input:focus { border-color: rgba(var(--bs-primary-rgb), .6); box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color); }
.form-check-input:checked { background-color: var(--bs-primary); border-color: var(--bs-primary); }
.nav-pills { --bs-nav-pills-link-active-bg: var(--bs-primary); }
.nav { --bs-nav-link-color: var(--bs-secondary-color); --bs-nav-link-hover-color: var(--bs-emphasis-color); }
.pagination { --bs-pagination-active-bg: var(--bs-primary); --bs-pagination-active-border-color: var(--bs-primary); --bs-pagination-color: var(--bs-body-color); --bs-pagination-focus-box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color); }
.progress, .progress-stacked { --bs-progress-bar-bg: var(--bs-primary); }
.list-group { --bs-list-group-active-bg: var(--bs-primary); --bs-list-group-active-border-color: var(--bs-primary); }
.dropdown-menu { --bs-dropdown-link-active-bg: var(--bs-primary); border-radius: .875rem; box-shadow: var(--wdw-shadow-sm); }
.accordion { --bs-accordion-active-bg: var(--bs-primary-bg-subtle); --bs-accordion-active-color: var(--bs-primary-text-emphasis); --bs-accordion-btn-focus-box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color); }
.badge { font-weight: 600; }
/* Library building blocks — use these instead of re-inventing them per snippet. */
.wdw-stage { min-height: 100vh; display: flex; align-items: center; background: var(--wdw-stage); padding-block: 3rem; }
.wdw-card { background: var(--bs-body-bg); border: 1px solid var(--bs-border-color-translucent); border-radius: var(--wdw-radius-card); box-shadow: var(--wdw-shadow); }
.wdw-card-sm { background: var(--bs-body-bg); border: 1px solid var(--bs-border-color-translucent); border-radius: 1rem; box-shadow: var(--wdw-shadow-sm); }
.wdw-aurora { position: relative; color: #fff; background: radial-gradient(60% 55% at 100% 0, #818cf8 0, transparent 70%), radial-gradient(65% 60% at 0 100%, #ec4899 0, transparent 65%), #1e1b4b; }
.wdw-eyebrow { display: inline-block; font-size: .75rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--bs-primary); }
.wdw-heading { font-weight: 800; letter-spacing: -.02em; color: var(--bs-emphasis-color); }
.wdw-muted { color: var(--bs-secondary-color); }
.wdw-icon { width: 2.75rem; height: 2.75rem; flex: none; display: inline-flex; align-items: center; justify-content: center; border-radius: .875rem; background: var(--bs-primary-bg-subtle); color: var(--bs-primary); }
/* Component */
.pg-cell { background: var(--bs-tertiary-bg); border-radius: 1rem; padding: 1.25rem; }
.pg { --bs-pagination-border-width: 0; --bs-pagination-bg: transparent; --bs-pagination-hover-bg: var(--bs-primary-bg-subtle); --bs-pagination-hover-color: var(--bs-primary); --bs-pagination-disabled-bg: transparent; --bs-pagination-focus-bg: var(--bs-primary-bg-subtle); gap: .25rem; flex-wrap: wrap; }
.pg .page-link { min-width: 2.75rem; min-height: 2.75rem; display: inline-flex; align-items: center; justify-content: center; font-weight: 600; padding: 0 .5rem; margin: 0 !important; }
.pg .page-link svg { display: block; }
.pg .active .page-link { color: #fff; box-shadow: 0 6px 14px -6px rgba(var(--bs-primary-rgb), .8); }
[data-bs-theme="dark"] .pg .active .page-link { color: #0f1117; }
.pg-pill .page-link { border-radius: 50% !important; padding: 0; }
.pg-seg { gap: 0; padding: .25rem; background: var(--bs-body-bg); border: 1px solid var(--bs-border-color); border-radius: 999px; flex-wrap: nowrap; width: max-content; max-width: 100%; }
.pg-seg .page-link { border-radius: 999px !important; }
.pg-sq { --bs-pagination-border-width: 1px; --bs-pagination-border-color: var(--bs-border-color); --bs-pagination-bg: var(--bs-body-bg); gap: .375rem; }
.pg-sq .page-link { border-radius: .625rem !important; }
.pg-sq .active .page-link { box-shadow: none; }
.pg-round { width: 2.75rem; padding: 0; border-radius: 50%; flex: none; background: var(--bs-body-bg); border: 1px solid var(--bs-border-color); color: var(--bs-body-color); }
.pg-round:hover { background: var(--bs-primary-bg-subtle); color: var(--bs-primary); border-color: var(--bs-primary-border-subtle); }
.pg-round-primary { background: var(--bs-primary); border-color: var(--bs-primary); color: #fff; }
.pg-round-primary:hover { background: var(--bs-primary); color: #fff; filter: brightness(.92); }
[data-bs-theme="dark"] .pg-round-primary, [data-bs-theme="dark"] .pg-round-primary:hover { color: #0f1117; }
.pg-round:disabled { opacity: .35; background: transparent; border-style: dashed; color: var(--bs-secondary-color); }
.pg-round-primary:disabled { border-style: solid; background: var(--bs-primary); color: #fff; }
[data-bs-theme="dark"] .pg-round-primary:disabled { color: #0f1117; }
.pg-dots { display: flex; justify-content: center; gap: .375rem; }
.pg-dots span { width: .5rem; height: .5rem; border-radius: 999px; background: var(--bs-border-color); transition: width .2s, background-color .2s; }
.pg-dots span.on { background: var(--bs-primary); }
.pg-dots span.cur { width: 1.5rem; }
.pg-progress { height: .5rem; border-radius: 999px; background: var(--bs-body-bg); }
.pg-progress .progress-bar { transition: width .3s; }
@media (max-width: 575.98px) {
.pg-cell { padding: 1rem; }
.pg-dense { gap: 0; justify-content: center; flex-wrap: nowrap; }
.pg-dense .page-link { min-width: 1.9rem; min-height: 2.5rem; padding: 0; }
.pg-seg .page-link, .pg-sq .page-link { min-width: 2.5rem; }
.pg-sq { gap: .25rem; flex-wrap: nowrap; }
.pg-sq .page-link { min-width: 2.25rem; padding: 0 .25rem; }
}
@media (prefers-reduced-motion: reduce) { .pg-dots span, .pg-progress .progress-bar { transition: none; } }
(function () {
var chev = function (d) {
return '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="' + (d < 0 ? 'm15 6-6 6 6 6' : 'm9 6 6 6-6 6') + '"/></svg>';
};
function makeItem(html, label) {
var li = document.createElement('li');
li.className = 'page-item';
var b = document.createElement('button');
b.type = 'button'; b.className = 'page-link'; b.innerHTML = html;
if (label) b.setAttribute('aria-label', label);
li.appendChild(b);
return { li: li, btn: b };
}
function windowFor(page, total) {
if (total <= 7) { var all = []; for (var i = 1; i <= total; i++) all.push(i); return all; }
if (page <= 4) return [1, 2, 3, 4, 5, 0, total];
if (page >= total - 3) return [1, 0, total - 4, total - 3, total - 2, total - 1, total];
return [1, 0, page - 1, page, page + 1, 0, total];
}
function initPagers() {
document.querySelectorAll('[data-pager]').forEach(function (ul) {
var total = +ul.dataset.total, page = 1;
var ellipsis = !!ul.dataset.ellipsis, arrows = !!ul.dataset.arrows;
var info = ul.dataset.info ? document.getElementById(ul.dataset.info) : null;
var items = +ul.dataset.items, per = +ul.dataset.per;
var slots = [], prev, next, targets = [];
var count = ellipsis ? Math.min(7, total) : total;
if (arrows) { prev = makeItem(chev(-1), 'Previous page'); ul.appendChild(prev.li); }
for (var i = 0; i < count; i++) { var s = makeItem('', ''); slots.push(s); targets.push(0); ul.appendChild(s.li); }
if (arrows) { next = makeItem(chev(1), 'Next page'); ul.appendChild(next.li); }
function go(p) { if (p >= 1 && p <= total && p !== page) { page = p; update(); } }
slots.forEach(function (s, idx) { s.btn.addEventListener('click', function () { if (targets[idx]) go(targets[idx]); }); });
if (arrows) {
prev.btn.addEventListener('click', function () { go(page - 1); });
next.btn.addEventListener('click', function () { go(page + 1); });
}
function setDisabled(it, off) { it.li.classList.toggle('disabled', off); it.btn.disabled = off; }
function update() {
var hadFocus = ul.contains(document.activeElement);
var model = windowFor(page, total), cur = null;
slots.forEach(function (s, idx) {
var v = model[idx];
targets[idx] = v;
if (!v) {
s.btn.textContent = '…'; s.btn.disabled = true; s.btn.removeAttribute('aria-label');
s.btn.setAttribute('aria-hidden', 'true'); s.btn.removeAttribute('aria-current');
s.li.className = 'page-item disabled';
return;
}
s.btn.removeAttribute('aria-hidden'); s.btn.disabled = false;
s.btn.textContent = String(v); s.btn.setAttribute('aria-label', 'Page ' + v);
var on = v === page;
s.li.className = 'page-item' + (on ? ' active' : '');
if (on) { s.btn.setAttribute('aria-current', 'page'); cur = s.btn; } else s.btn.removeAttribute('aria-current');
});
if (arrows) { setDisabled(prev, page === 1); setDisabled(next, page === total); }
if (info) {
var a = (page - 1) * per + 1, b = Math.min(items, page * per);
info.innerHTML = 'Showing <strong>' + a + '-' + b + '</strong> of <strong>' + items + '</strong> results';
}
if (hadFocus && cur && (ul.contains(document.activeElement) === false || document.activeElement.disabled)) cur.focus();
}
update();
});
}
function initMini() {
var nav = document.querySelector('[data-mini]');
if (!nav) return;
var total = +nav.dataset.total, page = 1;
var count = nav.querySelector('[data-count]'), dotsBox = nav.querySelector('[data-dots]');
var btns = nav.querySelectorAll('[data-dir]'), dots = [];
for (var i = 0; i < total; i++) { var d = document.createElement('span'); dotsBox.appendChild(d); dots.push(d); }
function render() {
count.textContent = 'Page ' + page + ' of ' + total;
dots.forEach(function (d, i) { d.className = (i < page ? 'on' : '') + (i === page - 1 ? ' cur' : ''); });
var had = nav.contains(document.activeElement);
btns[0].disabled = page === 1; btns[1].disabled = page === total;
if (had && document.activeElement.disabled) (page === 1 ? btns[1] : btns[0]).focus();
}
btns.forEach(function (b) {
b.addEventListener('click', function () { page = Math.min(total, Math.max(1, page + +b.dataset.dir)); render(); });
});
render();
}
function initLoadMore() {
var box = document.querySelector('[data-more]');
if (!box) return;
var total = +box.dataset.total, step = +box.dataset.step, shown = step;
var label = box.querySelector('[data-more-count]'), pct = box.querySelector('[data-more-pct]');
var bar = box.querySelector('[data-more-bar]'), btn = box.querySelector('[data-more-btn]');
var prog = box.querySelector('.progress');
function render() {
var p = Math.round(shown / total * 100);
label.textContent = 'Showing ' + shown + ' of ' + total + ' results';
pct.textContent = p + '%'; bar.style.width = p + '%'; prog.setAttribute('aria-valuenow', p);
if (shown >= total) { btn.textContent = 'All results loaded'; btn.setAttribute('aria-disabled', 'true'); btn.classList.add('disabled'); }
}
btn.addEventListener('click', function () {
if (shown >= total) return;
shown = Math.min(total, shown + step); render();
});
render();
}
initPagers(); initMini(); initLoadMore();
})();
Built on Bootstrap 5.3.8 with plain JavaScript — no jQuery. Supports light and dark mode via data-bs-theme.