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:

  1. Server-rendered HTML string from QueryService.getPageNavigation(...): used by news list pagination.jsp, individual profile list (via paginationHtml in the model), faculty profile search, and any JSP that includes news-events/news-list/pagination.jsp. Output is a <ul class="pagination"> with list items such as first, prev, numbered, current, next, last, and anchor links that preserve the query string (with page updated).
  2. HTL templates for directory person and department search: pagination.html beside each component, same outer ul.pagination idea but English labels like “First”, “Previous” in markup (see Code).
  3. Thunderstone faceted search: results include a .pagination list; tfs.js intercepts clicks on .c-tfs__results .pagination li a for AJAX behaviour and syncs the page query 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

Pagination anatomy

  1. List container: <ul class="pagination"> (flex-wrap supported in site LESS).
  2. Items: <li> with a class that describes the control (first, prev, numbered, current, next, last, or directory variants).
  3. Links: <a href="?page=…"> with optional data-page for scripting; current page may link to #results_top with aria-current="true" on the li (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:

ParentTypical controlNotes
News list (list / grid / faceted)Paginate, page size / limits via list logicIncludes pagination.jsp when enabled and results warrant it.
List events, list featured events, list related stories, list related programsPagination include in JSPSame shared pagination.jsp.
Zoteropagination checkbox in dialogConditionally includes pagination.jsp.
Thunderstone faceted search“Show pagination” in dialogWorks with tfs.js and result markup.
Individual profile listModel-drivenpaginationHtml from QueryService when applicable.
Directory searchHTLPage numbers and nav from PersonSearchModel / DepartmentSearchModel.

Instances in the codebase

PatternWhereHow pagination appears
Shared JSPnews-events/news-list/pagination.jspDelegates to QueryService.getPageNavigation using session attributes (itemsOnPage, limit, total) and page request parameter.
News list formatsformat_list.jsp, format_grid.jsp, format_faceted_search.jsp<cq:include script="pagination.jsp"/> when paginating.
List eventslist-events/list-events.jspIncludes pagination.jsp.
List featured eventslist-featured-events/list-featured-events.jspIncludes pagination.jsp.
List related storieslist-related-stories/list-related-stories.jspIncludes pagination.jsp.
List related programsacademics/list-related-programs/list-related-programs.jspIncludes pagination.jsp.
Zoterozotero/zotero.jspOptional include of pagination.jsp.
Individual profile listindividual-profile-list/individual-profile-list.htmlOutputs ${model.paginationHtml} built with QueryService in IndividualProfileListModel.
Faculty profile searchfaculty/faculty-profile-search/faculty-profile-search.jspCalls QueryService.getPageNavigation when total > limit.
Directory person searchdirectory/person-search/pagination.htmlHTL ul.pagination with First / Previous / numbered / Next / Last.
Directory department searchdepartment-search/pagination.htmlSame pattern; fallback alert if model missing.
Thunderstone facetedResults + thunderstone-faceted-search/js/tfs.jsClick 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 QueryService or HTL urlParams still round-trip with page.
  • 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.js resets pagination when filters change).

Don't

  • Don’t hand-roll a second pagination pattern on the same result set.
  • Don’t remove aria-current from the current page item when using QueryService output without replacing it.

Design system recommendations

Align pagination controls

  • Canonical visual: The site uses ul.pagination with links inside list items, styled under #boot in pagination.less (borders, hover, current in bold). That is not the same as Bootstrap 5’s page-item / page-link class 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.less rules. If you migrate markup to Bootstrap 5 nav classes, update both QueryService output 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.pagination clientlib is embedded in concordia-master-clientlibs; most server-driven lists reuse one getPageNavigation implementation; 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 QueryService uses 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 same ul.pagination structure.

Related

Resource
!AEMNews list, list events, individual profile list, faculty profile search, directory components, Zotero, Thunderstone faceted search.
!WCAGNavigation landmark practices and Understanding 2.4.8 Location.
!CDSOther 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 a uses bold font weight (not a separate fill colour in the shared LESS).

Design tokens

Token / variableCategoryWhere used
@color-light-greyborderLink borders on ul.pagination li a.
@color-light-grey-hoverbackgroundHover 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.pagination is enforced with !important in pagination.less to 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.less does not duplicate Bootstrap’s page-link:focus box-shadow; ensure keyboard focus remains visible (browser default or global focus styles on a).

Semantics

  • Pagination is a navigational control; wrapping in <nav aria-label="…"> is not present in the core QueryService string; consider adding it in a future refactor if the page has multiple nav regions.
  • 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 a after hover styles; add explicit :focus-visible rules if audits flag low contrast.

Testing

TestStatus
Keyboard through all pagination linksRecommended
Screen reader announces current pageRecommended
Page + filters together (news, faculty, Thunderstone)Recommended
Bilingual pages using QueryService vs directory HTLReview

WCAG / guidelines

  • HTML generation (server): org.concordia.wcms.core.services.query.QueryServiceImpl.getPageNavigation builds the ul.pagination string and optional debug comments.
  • HTML generation (HTL): directory/person-search/pagination.html, department-search/pagination.html.
  • Include bridge: news-events/news-list/pagination.jsp reads session attributes set by list logic and calls QueryService.
  • Styles: etc/designs/concordia/clientlibs/pagination/ (apps.concordia.pagination).

Implementation

FilePurpose
concordia-core/.../QueryServiceImpl.javagetPageNavigation overloads; builds <ul class='pagination'> with first, prev, numbered, current, next, last, data-page attributes, aria-current on current.
news-events/news-list/pagination.jspReads page, session totals, filterQuery; calls QueryService.getPageNavigation.
individual-profile-list + IndividualProfileListModelbuildPaginationHtml() delegates to QueryService.
faculty-profile-search.jspCalls getPageNavigation when results exceed page size.
directory/.../pagination.htmlHTL pagination for person and department search.
thunderstone-faceted-search/js/tfs.jsDelegates clicks on .c-tfs__results .pagination li a; reads page from query string.
pagination/less/pagination.lessSite 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 often href='#results_top' with aria-current='true' on the li.
  • Optional debug comment block with item counts (not shown when DEBUG is 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 before pagination.jsp runs).
  • Clientlib apps.concordia.pagination (embedded via concordia-master-clientlibs).