Pagination
Use pagination when long lists or search results should load in pages instead of one screen.
Visitors orient themselves in large result sets and return to a stable URL for a given page. Content authors usually turn pagination on in the parent component (news list, faceted search, directory, and similar), not via a separate “Pagination” block. Visual designers keep numbered links legible and current page obvious; avoid hiding all navigation on mobile.
Technical behaviour
There are three main mechanisms:
- Server-rendered HTML string from
QueryService.getPageNavigation(...): used by news listpagination.jsp, individual profile list (viapaginationHtmlin the model), faculty profile search, and any JSP that includesnews-events/news-list/pagination.jsp. Output is a<ul class="pagination">with list items such asfirst,prev,numbered,current,next,last, and anchor links that preserve the query string (withpageupdated). - HTL templates for directory person and department search:
pagination.htmlbeside each component, same outerul.paginationidea but English labels like “First”, “Previous” in markup (see Code). - Thunderstone faceted search: results include a
.paginationlist;tfs.jsintercepts clicks on.c-tfs__results .pagination li afor AJAX behaviour and syncs thepagequery parameter.
Authors typically enable pagination through the parent component dialog (for example “Paginate” on news, “Show pagination” on Thunderstone faceted search, or Zotero), not through a standalone Pagination component.
Anatomy

- List container:
<ul class="pagination">(flex-wrap supported in site LESS). - Items:
<li>with a class that describes the control (first,prev,numbered,current,next,last, or directory variants). - Links:
<a href="?page=…">with optionaldata-pagefor scripting; current page may link to#results_topwitharia-current="true"on theli(QueryService).
(Placeholder asset: add a screenshot or diagram of the numbered strip and First / Previous / Next / Last.)
Content
There is no dedicated “Pagination” dialog. Relevant options live on parents, for example:
| Parent | Typical control | Notes |
|---|---|---|
| News list (list / grid / faceted) | Paginate, page size / limits via list logic | Includes pagination.jsp when enabled and results warrant it. |
| List events, list featured events, list related stories, list related programs | Pagination include in JSP | Same shared pagination.jsp. |
| Zotero | pagination checkbox in dialog | Conditionally includes pagination.jsp. |
| Thunderstone faceted search | “Show pagination” in dialog | Works with tfs.js and result markup. |
| Individual profile list | Model-driven | paginationHtml from QueryService when applicable. |
| Directory search | HTL | Page numbers and nav from PersonSearchModel / DepartmentSearchModel. |
Instances in the codebase
| Pattern | Where | How pagination appears |
|---|---|---|
| Shared JSP | news-events/news-list/pagination.jsp | Delegates to QueryService.getPageNavigation using session attributes (itemsOnPage, limit, total) and page request parameter. |
| News list formats | format_list.jsp, format_grid.jsp, format_faceted_search.jsp | <cq:include script="pagination.jsp"/> when paginating. |
| List events | list-events/list-events.jsp | Includes pagination.jsp. |
| List featured events | list-featured-events/list-featured-events.jsp | Includes pagination.jsp. |
| List related stories | list-related-stories/list-related-stories.jsp | Includes pagination.jsp. |
| List related programs | academics/list-related-programs/list-related-programs.jsp | Includes pagination.jsp. |
| Zotero | zotero/zotero.jsp | Optional include of pagination.jsp. |
| Individual profile list | individual-profile-list/individual-profile-list.html | Outputs ${model.paginationHtml} built with QueryService in IndividualProfileListModel. |
| Faculty profile search | faculty/faculty-profile-search/faculty-profile-search.jsp | Calls QueryService.getPageNavigation when total > limit. |
| Directory person search | directory/person-search/pagination.html | HTL ul.pagination with First / Previous / numbered / Next / Last. |
| Directory department search | department-search/pagination.html | Same pattern; fallback alert if model missing. |
| Thunderstone faceted | Results + thunderstone-faceted-search/js/tfs.js | Click delegation on .c-tfs__results .pagination li a; pagination reset on filter changes. |
When to use
- When a list or grid would be too long on one screen and URL-based page state (
?page=) is acceptable for sharing and back/forward. - When the parent component already supports pagination in its dialog.
When not to use
- For short lists where pagination adds noise; consider “show all” or a shorter default page size in the parent.
- When infinite scroll or “load more” is a deliberate UX choice (not implemented by this shared pagination).
Guidelines
Do
- Keep query string preservation consistent when adding new filters so
QueryServiceor HTLurlParamsstill round-trip withpage. - Place pagination after the result list so tab order and screen reader order follow “results, then navigation”.
- For Thunderstone faceted flows, test filter change and page change together (
tfs.jsresets pagination when filters change).
Don't
- Don’t hand-roll a second pagination pattern on the same result set.
- Don’t remove
aria-currentfrom the current page item when usingQueryServiceoutput without replacing it.
Design system recommendations
Align pagination controls
- Canonical visual: The site uses
ul.paginationwith links inside list items, styled under#bootinpagination.less(borders, hover, current in bold). That is not the same as Bootstrap 5’spage-item/page-linkclass pairing, though Bootstrap pagination variables exist in the vendor bundle. - Recommendation: In Figma, spec one pagination strip (spacing, typography, current page treatment) and map it to the existing
pagination.lessrules. If you migrate markup to Bootstrap 5 nav classes, update bothQueryServiceoutput and directory HTL in one initiative so authors do not see two different chrome styles.
How the current implementation compares to DS expectations
- Strengths: Shared
apps.concordia.paginationclientlib is embedded in concordia-master-clientlibs; most server-driven lists reuse onegetPageNavigationimplementation; i18n for First / Previous / Next / Last in QueryService; debug comments can expose counts for authors in edit mode. - Gaps: Directory HTL uses fixed English strings in anchors for some labels, while
QueryServiceuses i18n; that can diverge in bilingual experiences. Markup is legacy list shape, not full Bootstrap 5 pagination components, so focus ring behaviour may differ from other nav components unless tested. Thunderstone faceted relies on JS for partial updates; visual parity depends on result templates including the sameul.paginationstructure.
Related
| Resource | |
|---|---|
| !AEM | News list, list events, individual profile list, faculty profile search, directory components, Zotero, Thunderstone faceted search. |
| !WCAG | Navigation landmark practices and Understanding 2.4.8 Location. |
| !CDS | Other items under Navigation in this folder. |
Pagination appearance is controlled mainly by pagination.less (#boot ul.pagination), not by authors. Vendor Bootstrap defines .pagination / .page-link as well, but concordia rules target ul.pagination li a directly.
Anatomy (visual)
- Horizontal list of inline-block items with wrapped rows (
flex-wrap,row-gap) on narrow widths. - Links: 1px border using
@color-light-grey, negative horizontal overlap for shared borders, hover background@color-light-grey-hover. - Current page:
li.current auses bold font weight (not a separate fill colour in the shared LESS).
Design tokens
| Token / variable | Category | Where used |
|---|---|---|
@color-light-grey | border | Link borders on ul.pagination li a. |
@color-light-grey-hover | background | Hover state for links. |
Spacing uses fixed pixel gaps (row-gap, margin-top, margin-left) in pagination.less.
Variants (style)
- QueryService adds optional HTML comments for “Displaying X–Y of Z” when DEBUG is true (wrapped in
<!-- -->on publish). - Directory HTL mirrors the list structure but label text is authored in templates (not i18n in the snippet reviewed).
Behaviours
- Hover: Background change on anchors.
- Thunderstone faceted: Pagination clicks handled in JS without full page reload where the script applies.
Layout and spacing
- Top margin on
ul.paginationis enforced with!importantinpagination.lessto separate pagination from list content above.
- QueryService marks the current page with
aria-current="true"on the<li class="current">and uses real links for other pages (good for opening in new tab, bookmarks). - Directory HTL should be reviewed for language: visible “First”, “Previous”, etc. may need alignment with site i18n.
- Focus: Custom
pagination.lessdoes not duplicate Bootstrap’spage-link:focusbox-shadow; ensure keyboard focus remains visible (browser default or global focus styles ona).
Semantics
- Pagination is a navigational control; wrapping in
<nav aria-label="…">is not present in the coreQueryServicestring; consider adding it in a future refactor if the page has multiplenavregions. - Current page is communicated via
aria-current.
Keyboard
- Tab moves through pagination links in DOM order.
- Enter follows the link (full navigation unless Thunderstone JS prevents default for AJAX).
Screen reader
- Users hear link text (i18n strings from QueryService where used).
- Debug-only HTML comments may aid authors in edit mode but are not exposed to assistive tech on publish.
Focus and visibility
- Verify focus visibility on
.pagination aafter hover styles; add explicit:focus-visiblerules if audits flag low contrast.
Testing
| Test | Status |
|---|---|
| Keyboard through all pagination links | Recommended |
| Screen reader announces current page | Recommended |
| Page + filters together (news, faculty, Thunderstone) | Recommended |
| Bilingual pages using QueryService vs directory HTL | Review |
WCAG / guidelines
- Understanding 2.4.8 Location (Level AAA) (pagination supports orientation in long lists).
- ARIA
aria-currentfor the active page.
- HTML generation (server):
org.concordia.wcms.core.services.query.QueryServiceImpl.getPageNavigationbuilds theul.paginationstring and optional debug comments. - HTML generation (HTL):
directory/person-search/pagination.html,department-search/pagination.html. - Include bridge:
news-events/news-list/pagination.jspreads session attributes set by list logic and callsQueryService. - Styles:
etc/designs/concordia/clientlibs/pagination/(apps.concordia.pagination).
Implementation
| File | Purpose |
|---|---|
concordia-core/.../QueryServiceImpl.java | getPageNavigation overloads; builds <ul class='pagination'> with first, prev, numbered, current, next, last, data-page attributes, aria-current on current. |
news-events/news-list/pagination.jsp | Reads page, session totals, filterQuery; calls QueryService.getPageNavigation. |
individual-profile-list + IndividualProfileListModel | buildPaginationHtml() delegates to QueryService. |
faculty-profile-search.jsp | Calls getPageNavigation when results exceed page size. |
directory/.../pagination.html | HTL pagination for person and department search. |
thunderstone-faceted-search/js/tfs.js | Delegates clicks on .c-tfs__results .pagination li a; reads page from query string. |
pagination/less/pagination.less | Site overrides for ul.pagination. |
Anatomy (markup)
QueryService (simplified):
- Outer:
<ul class='pagination'>. - Items:
<li class='first|prev|numbered|current|next|last'>with<a href='?page=n&...'>. Current page link oftenhref='#results_top'witharia-current='true'on theli. - Optional debug comment block with item counts (not shown when
DEBUGis false on publish).
Directory HTL: ul.pagination > li.first / prev / numeric / next / last with English text in anchor content.
Authoring
Configure pagination on the parent component (news, Zotero, Thunderstone faceted search, etc.). There is no concordia/components/pagination resource type for pages.
Dependencies
- QueryService OSGi service (concordia-core).
- Session attributes for news-style flows:
itemsOnPage,limit,total(set by including components beforepagination.jspruns). - Clientlib
apps.concordia.pagination(embedded via concordia-master-clientlibs).