Breadcrumbs and Pagination

Beginner
9 min

Breadcrumbs and Pagination

Breadcrumbs tell users where they are in a site's hierarchy; pagination lets them move through long result sets. Both are pure CSS components with no JavaScript, and both have a small number of options that are easy to get right once you know the accessibility requirements. This lesson covers the markup, dividers, sizes, alignment and how to generate pagination from data.

Breadcrumbs

html
<nav aria-label="breadcrumb"> <ol class="breadcrumb"> <li class="breadcrumb-item"><a href="/">Home</a></li> <li class="breadcrumb-item"><a href="/library">Library</a></li> <li class="breadcrumb-item active" aria-current="page">Data</li> </ol> </nav>
  • Wrap the list in <nav aria-label="breadcrumb"> so screen readers announce it as a navigation landmark.
  • The last item is the current page: give it .active and aria-current="page" and do not make it a link.
  • Dividers are added with CSS ::before, so no separator characters appear in the HTML.

Changing the Divider

The divider is a CSS custom property. Override it inline or on any ancestor:

html
<nav aria-label="breadcrumb" style="--bs-breadcrumb-divider: '>'"> <ol class="breadcrumb">...</ol> </nav>

Use an SVG for a chevron icon or an empty string to remove the divider entirely:

css
.breadcrumb { --bs-breadcrumb-divider: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='%236c757d'/%3E%3C/svg%3E"); } .breadcrumb.no-divider { --bs-breadcrumb-divider: ''; }

Changing it globally is a Sass override of $breadcrumb-divider before importing Bootstrap.

Pagination

Pagination uses a <ul class="pagination"> of .page-item elements, each containing a .page-link:

html
<nav aria-label="Article pages"> <ul class="pagination"> <li class="page-item"> <a class="page-link" href="?page=1" aria-label="Previous"> <span aria-hidden="true">&laquo;</span> </a> </li> <li class="page-item"><a class="page-link" href="?page=1">1</a></li> <li class="page-item active" aria-current="page"><a class="page-link" href="?page=2">2</a></li> <li class="page-item"><a class="page-link" href="?page=3">3</a></li> <li class="page-item"> <a class="page-link" href="?page=3" aria-label="Next"> <span aria-hidden="true">&raquo;</span> </a> </li> </ul> </nav>

Guidelines:

  • Use real href values so pages work without JavaScript and can be opened in a new tab.
  • Mark the current page with .active and aria-current="page".
  • Disable unavailable links with .disabled on the item and aria-disabled="true" on the link; Bootstrap adds pointer-events: none. Prefer <span> or <button disabled> for truly non-clickable items.
  • Icons such as &laquo; must be wrapped in aria-hidden="true" with an aria-label on the link.

Sizes and Alignment

| Class | Effect | |---|---| | pagination-sm | Smaller links | | pagination-lg | Larger links | | justify-content-center | Center the list (it is a flex container) | | justify-content-end | Right-align |

html
<ul class="pagination pagination-sm justify-content-end">...</ul>

Pagination links use CSS variables (--bs-pagination-active-bg, --bs-pagination-color, and others), so a themed variant is one CSS rule away:

css
.pagination.brand { --bs-pagination-active-bg: #4f46e5; --bs-pagination-active-border-color: #4f46e5; --bs-pagination-color: #4f46e5; }

Generating Pagination from Data

Most real pagination is rendered from a page count. A compact JavaScript helper that shows a window of pages around the current one:

javascript
function renderPagination(current, total, hrefFor) { const items = []; const push = (label, page, opts = {}) => { const cls = ["page-item", opts.active && "active", opts.disabled && "disabled"].filter(Boolean).join(" "); const aria = opts.active ? ' aria-current="page"' : opts.disabled ? ' aria-disabled="true"' : ""; items.push(`<li class="${cls}"><a class="page-link" href="${hrefFor(page)}"${aria}>${label}</a></li>`); }; push("Previous", current - 1, { disabled: current === 1 }); for (let p = Math.max(1, current - 2); p <= Math.min(total, current + 2); p++) { push(p, p, { active: p === current }); } push("Next", current + 1, { disabled: current === total }); return `<nav aria-label="Pages"><ul class="pagination">${items.join("")}</ul></nav>`; } document.getElementById("pager").innerHTML = renderPagination(7, 20, (p) => `?page=${p}`);

Server-side templates (EJS, Blade, Jinja) follow the same shape.

Common Mistakes

  • Making the active breadcrumb a link. The current page should be plain text.
  • Omitting the <nav> wrapper. Both components need it for the landmark and aria-label.
  • Using .disabled without aria-disabled. The style is applied but assistive technology still reports a normal link.
  • Rendering hundreds of page links. Show a window around the current page plus first/last.
Quick Quiz
Question 1 of 3

How do you change the breadcrumb separator to `>` for a single breadcrumb?

Key Takeaways

  • Breadcrumbs are an <ol class="breadcrumb"> inside <nav aria-label="breadcrumb">; the last item gets .active and aria-current="page".
  • The divider is controlled by --bs-breadcrumb-divider, so it can be changed per instance or globally.
  • Pagination is a <ul class="pagination"> of .page-item > .page-link, with .active, .disabled and matching ARIA attributes.
  • pagination-sm/-lg change size; flex utilities align the list; CSS variables restyle colors.
  • Generate a window of pages around the current one rather than listing every page.

Next lesson: Modals, Tooltips and Popovers - dialogs and contextual hints powered by Bootstrap's JavaScript plugins.

Breadcrumbs and Pagination - Bootstrap | CodeYourCraft | CodeYourCraft