Skip to content

Evaluate a pagination component (upstream pico discussion #368) #62

Description

@ricpastori

Source

Upstream: picocss/pico discussion #368 (11 upvotes, 0 comments — asked, never answered), found during a sweep of picocss/pico's Discussions for items not yet covered by cirth's M5 audit.

Problem

No pagination pattern exists in cirth today (checked: no matches for "pagination" anywhere in src/). It's a common content-navigation need (docs sites, blogs, tables, search results) and reasonably cheap to design well: essentially a <nav> + list of page links/buttons, in the same spirit as the existing .group component (joined, adjacent controls) rather than a net-new mechanism.

Ask

Design a minimal, classless-first pagination pattern:

  • Likely <nav aria-label="Pagination"><ul>...<li><a aria-current="page">...</a> — reuse the existing [aria-current] handling already wired into nav/button styling rather than inventing new state markup.
  • Decide whether it needs its own component file or is a documented composition of existing primitives (nav + group-like joining) — lean toward the latter if it gets you 90% of the value with zero new CSS surface, consistent with how .breakout/.sr-only were scoped in M3.

Acceptance

  • A written decision on scope (new component vs. documented composition of existing primitives).
  • If new markup/CSS is needed: docs page, live demo, a11y-audited.
  • If it's a composition: a docs page (likely under Examples, gh#29) showing the recommended markup.

Activity

  1. added
    🧭 decision neededNeeds a product, API, or maintainership decision before implementation.
    ⬆️ upstreamInherited from or coordinated with the upstream Pico CSS project.
    on Aug 10, 2026
  2. moved this from 📝 Todo to 📥 Backlog in Cirth roadmapon Aug 10, 2026
  3. moved this from 📥 Backlog to 📝 Todo in Cirth roadmapon Aug 10, 2026
  4. ricpastori commented on Aug 15, 2026

    @ricpastori
    CollaboratorAuthor

    Proposed direction

    Pagination is a good fit for Cirth because its complete interaction can be implemented with native links and navigation semantics, without JavaScript or hidden state.

    The main design decision is how Cirth should identify a pagination navigation region for styling.

    The available options are:

    1. document pagination as a composition of existing HTML and Cirth primitives;
    2. infer pagination through structural selectors and semantic attributes;
    3. introduce an explicit .pagination class.

    Unlike tabs, all three options preserve native and accessible functionality. The choice is mainly about API design and consistency with Cirth’s philosophy.

    Recommended markup

    The semantic foundation should remain the same regardless of the styling option:

    <nav class="pagination" aria-label="Pagination">
      <ul>
        <li>
          <a href="?page=2" rel="prev">Previous</a>
        </li>
        <li>
          <a href="?page=1">1</a>
        </li>
        <li>
          <a href="?page=2">2</a>
        </li>
        <li>
          <strong aria-current="page">3</strong>
        </li>
        <li>
          <a href="?page=4">4</a>
        </li>
        <li>
          <a href="?page=4" rel="next">Next</a>
        </li>
      </ul>
    </nav>

    If no dedicated class is adopted, the same markup remains valid without it:

    <nav aria-label="Pagination">
      <!-- Pagination links -->
    </nav>

    The current page may also remain a link:

    <a href="?page=3" aria-current="page">3</a>

    Both approaches can be valid:

    • a non-link avoids offering a redundant navigation action;
    • a link allows the current URL to be copied or reloaded;
    • aria-current="page" identifies the current item programmatically.

    Reference: WAI-ARIA aria-current

    The navigation landmark should have an accessible name, especially when the document contains other navigation regions.

    Reference: WAI-ARIA Navigation Landmark

    The accessible name must be localized by the application.

    Previous and next links

    When a previous or next page is unavailable, the safest options are:

    • omit the unavailable control; or
    • render it as non-interactive text when preserving the layout is useful.

    For example:

    <li>
      <span>Previous</span>
    </li>

    I would avoid an active link with only aria-disabled="true":

    <a href="?page=0" aria-disabled="true">Previous</a>

    aria-disabled communicates a state but does not cancel navigation. Similarly, pointer-events: none only affects pointer interaction and does not provide an equivalent keyboard-disabled state.

    Ellipses

    An ellipsis representing omitted pages should not be interactive:

    <li aria-hidden="true">…</li>

    If it opens a page-selection control, it is no longer presentational and should instead be implemented as a correctly labelled button.

    Option A: composition of existing primitives

    Cirth could document pagination without adding a dedicated selector.

    The pattern would rely on the existing styles for:

    • <nav>;
    • <ul> and <li>;
    • links;
    • [aria-current="page"];
    • focus indication;
    • target sizing.

    Advantages:

    • no new class;
    • no new component API;
    • compatible with the classless build;
    • markup remains entirely semantic;
    • very small maintenance cost.

    Trade-off:

    • Cirth cannot reliably apply pagination-specific alignment, spacing or responsive behaviour without also affecting other navigation lists.

    This option is the most strictly semantic, but it may only provide an example rather than a recognisable pagination component.

    Option B: infer pagination through semantic structure

    Cirth could infer the component from rel="prev" or rel="next":

    @supports selector(nav:has(a[rel~="next"])) {
      nav:has(a[rel~="prev"], a[rel~="next"]) > ul {
        /* Pagination layout */
      }
    }

    Advantages:

    • no class or custom attribute;
    • locale-independent;
    • semantic rel values serve their actual purpose;
    • browsers without :has() retain an ordinary navigation list.

    Trade-offs:

    • the selector is heuristic;
    • it may match another navigation region containing previous or next relationships;
    • it makes a specific DOM structure part of the styling API;
    • it does not clearly communicate author intent;
    • pages without either link cannot be identified as pagination;
    • it adds selector complexity to avoid a very small class.

    The functionality degrades safely, but the implicit detection may be less predictable than the rest of the framework.

    Option C: .pagination

    Cirth already includes a deliberately small set of classes such as:

    • .secondary;
    • .outline;
    • .container;
    • .container-fluid;
    • .grid;
    • .row.

    The project philosophy states that:

    Classes are an escape hatch, not a language.

    The default build includes a small number of classes for distinctions that HTML cannot express, while the classless build includes none.

    Reference: [About Cirth — Why semantic HTML first](https://cirthcss.github.io/cirth/about/)

    HTML can express that the element is navigation, and aria-current, rel="prev" and rel="next" express important relationships. However, HTML has no dedicated pagination element or pagination role. It cannot explicitly distinguish a pagination <nav> from site navigation, breadcrumbs, table-of-contents navigation or other link collections in a way that is both reliable and suitable as a CSS selector.

    This means .pagination could reasonably qualify as one of the limited cases where a class expresses a distinction that HTML cannot express on its own:

    <nav class="pagination" aria-label="Pagination">
    nav.pagination > ul {
      /* Pagination layout */
    }
    
    nav.pagination [aria-current="page"] {
      /* Current page presentation */
    }

    Advantages:

    • explicit author intent;
    • simple and stable selectors;
    • no dependency on localized text;
    • no structural heuristic;
    • consistent with the existing class-based escape hatches;
    • easy to document and customize;
    • removing the class or stylesheet leaves correct semantic HTML.

    Trade-offs:

    • adds another term to Cirth’s class vocabulary;
    • is unavailable as a dedicated component in the classless build;
    • risks contributing to a component catalog if similar classes are added without a strict threshold;
    • some generic navigation styling may already be sufficient.

    The relevant question is therefore not whether Cirth permits classes—it already does—but whether pagination is common and distinct enough to justify one of the framework’s intentionally limited escape hatches.

    Why .pagination may be preferable to data-pagination

    An explicit data attribute would also work:

    <nav data-pagination aria-label="Pagination">

    However, if Cirth decides that an explicit hook is necessary, .pagination is probably more consistent with the existing public API.

    The framework already uses named classes for HTML distinctions that cannot be represented natively. Introducing a data attribute for a purely presentational component distinction would create a second convention without providing a clear advantage.

    Therefore, the choice should probably be between:

    • no explicit hook; or
    • the conventional .pagination class.

    Rejected option: using the accessible name

    Cirth should not use:

    nav[aria-label="Pagination"]

    The accessible name is content, not a styling API.

    Its value may legitimately be:

    <nav aria-label="Results pages">
    <nav aria-label="Product pages">
    <nav aria-label="Paginazione">

    Using the label as a selector would couple visual presentation to language and author wording.

    A non-standard role such as role="pagination" should not be introduced either.

    Recommended styling scope

    Whether or not .pagination is adopted, Cirth can provide or document:

    • visual treatment for [aria-current="page"];
    • sufficient link padding;
    • visible keyboard focus;
    • wrapping on narrow viewports;
    • spacing between adjacent targets;
    • logical source and reading order;
    • forced-colours compatibility.

    Pagination links should have an active area of at least 24 × 24 CSS pixels or satisfy the applicable spacing exception. Cirth’s documented 44px accessibility floor would exceed this minimum.

    Reference: [Understanding WCAG 2.2 SC 2.5.8](https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum)

    No functionality should depend on pagination-specific CSS.

    Progressive degradation

    All three approaches degrade safely.

    With .pagination:

    • the default build provides the dedicated presentation;
    • the classless build presents an ordinary navigation list;
    • removing Cirth leaves valid and functional HTML;
    • browsers without advanced CSS still expose every link;
    • ARIA support does not affect navigation functionality;
    • no hidden panel or overlay can become permanently visible.

    This matches the principle described on the About page: markup should remain correct, accessible and machine-readable if the stylesheet is removed.

    !important policy

    No !important declarations are necessary. Pagination can be implemented with ordinary selectors, source order and the existing cascade.

    Recommended decision

    Implement pagination as a semantic HTML pattern.

    Before implementation, decide whether pagination-specific presentation is substantial enough to justify an explicit API:

    • if existing navigation styles are sufficient, document the pattern without a class;
    • if pagination needs its own layout, responsive rules and state presentation, adopt nav.pagination;
    • avoid localization-dependent selectors;
    • avoid non-standard ARIA roles;
    • prefer .pagination over a data-pagination attribute if an explicit hook is required;
    • keep the class absent from the classless build, where the semantic navigation remains fully functional.

    My preference would be .pagination if the final design is visually or structurally distinct from an ordinary <nav>. It appears consistent with the stated policy that classes are reserved as escape hatches for distinctions HTML cannot express, while still preserving Cirth’s semantic-first baseline.

  5. moved this from 📝 Todo to 📥 Backlog in Cirth roadmapon Aug 15, 2026
  6. moved this from 📥 Backlog to 🚧 In progress in Cirth roadmapon Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ⬆️ upstreamInherited from or coordinated with the upstream Pico CSS project.🧭 decision neededNeeds a product, API, or maintainership decision before implementation.

    Fields

    Priority

    🌱 P3 — Low

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions