Navigation

Pagination

Links to the pages of a long list, with previous and next, for results split across pages.

Built on
<nav><a href><button>aria-current
Client JavaScript
None
Works without React
Yes, as HTML and CSS
Source, gzipped
2.9 kB
Dependencies
React
Try its options

Loading its options.

Code
import { Pagination } from "@/components/equip/pagination";

export default function Demo() {
  return <Pagination page={6} count={12} siblings={0} href={(n) => `#invoices?page=${n}`} />;
}
Your versionchange its options in Look, This part
HTMLworks in any framework, with the part's CSS
<nav aria-label="Pagination" class="eq-pagination">
  <ul class="eq-pagination-list">
    <li>
      <a class="eq-pagination-step" data-dir="previous" aria-label="Previous page" rel="prev" href="#invoices?page=5">
        <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <path d="M10 4l-4 4 4 4" />
        </svg>
      </a>
    </li>
    <li>
      <a class="eq-pagination-link" aria-label="Page 1" href="#invoices?page=1">1</a>
    </li>
    <li>
      <span class="eq-pagination-gap" aria-hidden="true">…</span>
    </li>
    <li>
      <a class="eq-pagination-link" aria-label="Page 6" aria-current="page" href="#invoices?page=6">6</a>
    </li>
    <li>
      <span class="eq-pagination-gap" aria-hidden="true">…</span>
    </li>
    <li>
      <a class="eq-pagination-link" aria-label="Page 12" href="#invoices?page=12">12</a>
    </li>
    <li>
      <a class="eq-pagination-step" data-dir="next" aria-label="Next page" rel="next" href="#invoices?page=7">
        <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <path d="M6 4l4 4-4 4" />
        </svg>
      </a>
    </li>
  </ul>
</nav>

Examples

First and last page

At either end the step link loses its href, so it drops out of the tab order and reads as disabled.

Code
import { Pagination } from "@/components/equip/pagination";

export default function Example() {
  const href = (n: number) => `#audit-log?page=${n}`;
  return (
    <div style={{ display: "grid", gap: 12 }}>
      <Pagination aria-label="Audit log, first page" page={1} count={48} href={href} />
      <Pagination aria-label="Audit log, last page" page={48} count={48} href={href} />
    </div>
  );
}
HTMLworks in any framework, with the part's CSS
<div style="display:grid;gap:12px">
  <nav aria-label="Audit log, first page" class="eq-pagination">
    <ul class="eq-pagination-list">
      <li>
        <a class="eq-pagination-step" data-dir="previous" role="link" aria-disabled="true" aria-label="Previous page">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M10 4l-4 4 4 4" />
          </svg>
        </a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 1" aria-current="page" href="#audit-log?page=1">1</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 2" href="#audit-log?page=2">2</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 3" href="#audit-log?page=3">3</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 4" href="#audit-log?page=4">4</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 5" href="#audit-log?page=5">5</a>
      </li>
      <li>
        <span class="eq-pagination-gap" aria-hidden="true">…</span>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 48" href="#audit-log?page=48">48</a>
      </li>
      <li>
        <a class="eq-pagination-step" data-dir="next" aria-label="Next page" rel="next" href="#audit-log?page=2">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M6 4l4 4-4 4" />
          </svg>
        </a>
      </li>
    </ul>
  </nav>
  <nav aria-label="Audit log, last page" class="eq-pagination">
    <ul class="eq-pagination-list">
      <li>
        <a class="eq-pagination-step" data-dir="previous" aria-label="Previous page" rel="prev" href="#audit-log?page=47">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M10 4l-4 4 4 4" />
          </svg>
        </a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 1" href="#audit-log?page=1">1</a>
      </li>
      <li>
        <span class="eq-pagination-gap" aria-hidden="true">…</span>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 44" href="#audit-log?page=44">44</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 45" href="#audit-log?page=45">45</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 46" href="#audit-log?page=46">46</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 47" href="#audit-log?page=47">47</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 48" aria-current="page" href="#audit-log?page=48">48</a>
      </li>
      <li>
        <a class="eq-pagination-step" data-dir="next" role="link" aria-disabled="true" aria-label="Next page">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M6 4l4 4-4 4" />
          </svg>
        </a>
      </li>
    </ul>
  </nav>
</div>

Neighbours

siblings sets how many pages show beside the current one; the width stays fixed as you move through the middle.

Code
import { Pagination } from "@/components/equip/pagination";

export default function Example() {
  const href = (n: number) => `#deployments?page=${n}`;
  return (
    <div style={{ display: "grid", gap: 12 }}>
      <Pagination aria-label="Deployments, no neighbours" page={24} count={60} siblings={0} href={href} />
      <Pagination aria-label="Deployments, one neighbour" page={24} count={60} href={href} />
      <Pagination aria-label="Deployments, two neighbours" page={24} count={60} siblings={2} href={href} />
    </div>
  );
}
HTMLworks in any framework, with the part's CSS
<div style="display:grid;gap:12px">
  <nav aria-label="Deployments, no neighbours" class="eq-pagination">
    <ul class="eq-pagination-list">
      <li>
        <a class="eq-pagination-step" data-dir="previous" aria-label="Previous page" rel="prev" href="#deployments?page=23">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M10 4l-4 4 4 4" />
          </svg>
        </a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 1" href="#deployments?page=1">1</a>
      </li>
      <li>
        <span class="eq-pagination-gap" aria-hidden="true">…</span>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 24" aria-current="page" href="#deployments?page=24">24</a>
      </li>
      <li>
        <span class="eq-pagination-gap" aria-hidden="true">…</span>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 60" href="#deployments?page=60">60</a>
      </li>
      <li>
        <a class="eq-pagination-step" data-dir="next" aria-label="Next page" rel="next" href="#deployments?page=25">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M6 4l4 4-4 4" />
          </svg>
        </a>
      </li>
    </ul>
  </nav>
  <nav aria-label="Deployments, one neighbour" class="eq-pagination">
    <ul class="eq-pagination-list">
      <li>
        <a class="eq-pagination-step" data-dir="previous" aria-label="Previous page" rel="prev" href="#deployments?page=23">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M10 4l-4 4 4 4" />
          </svg>
        </a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 1" href="#deployments?page=1">1</a>
      </li>
      <li>
        <span class="eq-pagination-gap" aria-hidden="true">…</span>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 23" href="#deployments?page=23">23</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 24" aria-current="page" href="#deployments?page=24">24</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 25" href="#deployments?page=25">25</a>
      </li>
      <li>
        <span class="eq-pagination-gap" aria-hidden="true">…</span>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 60" href="#deployments?page=60">60</a>
      </li>
      <li>
        <a class="eq-pagination-step" data-dir="next" aria-label="Next page" rel="next" href="#deployments?page=25">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M6 4l4 4-4 4" />
          </svg>
        </a>
      </li>
    </ul>
  </nav>
  <nav aria-label="Deployments, two neighbours" class="eq-pagination">
    <ul class="eq-pagination-list">
      <li>
        <a class="eq-pagination-step" data-dir="previous" aria-label="Previous page" rel="prev" href="#deployments?page=23">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M10 4l-4 4 4 4" />
          </svg>
        </a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 1" href="#deployments?page=1">1</a>
      </li>
      <li>
        <span class="eq-pagination-gap" aria-hidden="true">…</span>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 22" href="#deployments?page=22">22</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 23" href="#deployments?page=23">23</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 24" aria-current="page" href="#deployments?page=24">24</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 25" href="#deployments?page=25">25</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 26" href="#deployments?page=26">26</a>
      </li>
      <li>
        <span class="eq-pagination-gap" aria-hidden="true">…</span>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 60" href="#deployments?page=60">60</a>
      </li>
      <li>
        <a class="eq-pagination-step" data-dir="next" aria-label="Next page" rel="next" href="#deployments?page=25">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M6 4l4 4-4 4" />
          </svg>
        </a>
      </li>
    </ul>
  </nav>
</div>

Under a results table

Every page is a real link, so it works with JavaScript off and each page can be bookmarked.

Showing 41 to 60 of 1,284 invoices
Code
import { Pagination } from "@/components/equip/pagination";

export default function Example() {
  const page = 3;
  const perPage = 20;
  const total = 1284;
  return (
    <div
      style={{
        display: "flex",
        flexWrap: "wrap",
        alignItems: "center",
        justifyContent: "space-between",
        gap: 12,
        fontSize: "var(--eq-text-s)",
        color: "var(--eq-ink-2)",
      }}
    >
      <span>
        Showing {(page - 1) * perPage + 1} to {page * perPage} of {total.toLocaleString("en-US")} invoices
      </span>
      <Pagination
        aria-label="Invoices"
        page={page}
        count={Math.ceil(total / perPage)}
        href={(n) => `#invoices?page=${n}`}
      />
    </div>
  );
}
HTMLworks in any framework, with the part's CSS
<div style="display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px;font-size:var(--eq-text-s);color:var(--eq-ink-2)">
  <span>Showing 41 to 60 of 1,284 invoices</span>
  <nav aria-label="Invoices" class="eq-pagination">
    <ul class="eq-pagination-list">
      <li>
        <a class="eq-pagination-step" data-dir="previous" aria-label="Previous page" rel="prev" href="#invoices?page=2">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M10 4l-4 4 4 4" />
          </svg>
        </a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 1" href="#invoices?page=1">1</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 2" href="#invoices?page=2">2</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 3" aria-current="page" href="#invoices?page=3">3</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 4" href="#invoices?page=4">4</a>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 5" href="#invoices?page=5">5</a>
      </li>
      <li>
        <span class="eq-pagination-gap" aria-hidden="true">…</span>
      </li>
      <li>
        <a class="eq-pagination-link" aria-label="Page 65" href="#invoices?page=65">65</a>
      </li>
      <li>
        <a class="eq-pagination-step" data-dir="next" aria-label="Next page" rel="next" href="#invoices?page=4">
          <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M6 4l4 4-4 4" />
          </svg>
        </a>
      </li>
    </ul>
  </nav>
</div>

As buttons

With onPageChange and no href the pages are buttons, for a list paged in place where the address does not change.

Events 1 to 8 of 74
Code
"use client";

import { useState } from "react";
import { Pagination } from "@/components/equip/pagination";

const perPage = 8;
const total = 74;

export default function Example() {
  const [page, setPage] = useState(1);
  const count = Math.ceil(total / perPage);
  const from = (page - 1) * perPage + 1;
  const to = Math.min(page * perPage, total);
  return (
    <div style={{ display: "grid", gap: 8, justifyItems: "start" }}>
      <Pagination aria-label="Audit log pages" page={page} count={count} onPageChange={setPage} />
      <span style={{ fontSize: "var(--eq-text-s)", color: "var(--eq-ink-2)" }} aria-live="polite">
        Events {from} to {to} of {total}
      </span>
    </div>
  );
}

Props

PropTypeDefaultWhat it does
pagenumbernoneThe current page, counting from 1. Out-of-range values are clamped.
countnumbernoneHow many pages there are.
href(page: number) => stringnoneBuilds the link for a page. Pass it from a server component, or wrap Pagination in your own client component. Required unless onPageChange is given.
onPageChange(page: number) => voidnoneCalled with the page chosen. Without href the pages render as buttons and nothing navigates; with href they stay links and a plain click calls this instead of following the link.
siblingsnumber1Pages shown on each side of the current one.
previousLabelstring"Previous page"Accessible name of the previous link, which shows only an icon.
nextLabelstring"Next page"Accessible name of the next link, which shows only an icon.
aria-labelstring"Pagination"Names the landmark. Make it specific when a page has more than one.
...restComponentProps<"nav">noneEvery native nav attribute.

Anatomy

root.eq-paginationA nav landmark.
list.eq-pagination-list
step.eq-pagination-stepPrevious and next, told apart by data-dir. A button when there is no href.
link.eq-pagination-linkOne page, a link or, without href, a button. The current one has aria-current="page".
gap.eq-pagination-gapStands for two or more pages left out. Hidden from assistive tech.

Keyboard

EnterFollows the focused link.
EnterSpaceWithout href, goes to the focused page button.

Notes

  • A pure function of page, count and href: it renders links on the server and ships no JavaScript.
  • The range helper paginationRange is exported from the same file and keeps the control the same width as the page moves.
  • Page links are named "Page 3" and so on, which still contains the visible number.
  • On the first and last page the step link has no href, so it is announced as a disabled link and cannot be focused.
  • With onPageChange and no href, the pages are buttons that change state in place, such as a table paged on the client.
  • In button mode a step with nowhere to go keeps focus and sets aria-disabled, so focus is not lost on the last page.
  • Only href works without client JavaScript, since onPageChange can only come from a client component.

Source

Two files, and they are the whole part. Open either to read it, copy it straight from its bar, or add the part to your loadout and install it with everything else you picked.

components/equip/pagination.tsx210 lines
import type { ComponentProps, MouseEvent, ReactNode } from "react";
import "./pagination.css";

/**
 * The pages to show, in order, with "gap" where a run of two or more pages is
 * left out. The first and last page are always present, the current page has
 * `siblings` neighbours on each side, and the result is always the same length
 * (2 * siblings + 5) once count is large enough, so the control never jumps in width.
 * A single hidden page is shown instead of a gap, since a gap would take as much room.
 */
export function paginationRange(page: number, count: number, siblings = 1): (number | "gap")[] {
  const total = Math.max(0, Math.floor(count));
  if (total < 1) return [];
  const current = Math.min(Math.max(Math.round(page), 1), total);
  const side = Math.max(0, Math.floor(siblings));
  const slots = side * 2 + 5;
  const run = (from: number, to: number) => Array.from({ length: to - from + 1 }, (_, i) => from + i);

  if (total <= slots) return run(1, total);

  const left = Math.max(current - side, 1);
  const right = Math.min(current + side, total);
  const gapLeft = left > 3;
  const gapRight = right < total - 2;

  if (!gapLeft) return [...run(1, slots - 2), "gap", total];
  if (!gapRight) return [1, "gap", ...run(total - (slots - 2) + 1, total)];
  return [1, "gap", ...run(left, right), "gap", total];
}

type PaginationCommon = ComponentProps<"nav"> & {
  /** The current page, counting from 1. */
  page: number;
  /** How many pages there are. */
  count: number;
  /** Pages shown on each side of the current one. */
  siblings?: number;
  /** Accessible name of the previous link. */
  previousLabel?: string;
  /** Accessible name of the next link. */
  nextLabel?: string;
};

export type PaginationLinkProps = PaginationCommon & {
  /** Builds the link for a page, e.g. (n) => `/invoices?page=${n}`. */
  href: (page: number) => string;
  /** Also called on a plain click of a link, which then does not navigate. Modified clicks still open the link. */
  onPageChange?: (page: number) => void;
};

export type PaginationButtonProps = PaginationCommon & {
  href?: undefined;
  /** Without href, the pages are buttons that call this instead of navigating. */
  onPageChange: (page: number) => void;
};

export type PaginationProps = PaginationLinkProps | PaginationButtonProps;

type Go = { href?: (page: number) => string; onPageChange?: (page: number) => void };

/** Props for a page link; with onPageChange too, a plain click calls it instead of navigating. */
function linkTo(href: (page: number) => string, onPageChange: ((page: number) => void) | undefined, page: number) {
  return {
    href: href(page),
    ...(onPageChange
      ? {
          onClick: (event: MouseEvent<HTMLAnchorElement>) => {
            // Let the browser handle new tabs, new windows and downloads.
            if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
            event.preventDefault();
            onPageChange(page);
          },
        }
      : {}),
  };
}

/** Props for a page button. */
function buttonTo(onPageChange: ((page: number) => void) | undefined, page: number) {
  return onPageChange ? { onClick: () => onPageChange(page) } : {};
}

export function Pagination({
  page,
  count,
  href,
  onPageChange,
  siblings = 1,
  previousLabel = "Previous page",
  nextLabel = "Next page",
  className,
  "aria-label": label = "Pagination",
  ...rest
}: PaginationProps) {
  const range = paginationRange(page, count, siblings);
  const last = Math.max(0, Math.floor(count));
  const current = Math.min(Math.max(Math.round(page), 1), Math.max(1, last));
  const go: Go = { href, onPageChange };
  const buttons = href === undefined;

  return (
    <nav {...rest} aria-label={label} className={["eq-pagination", className].filter(Boolean).join(" ")}>
      <ul className="eq-pagination-list">
        <li>
          <Step go={go} to={current > 1 ? current - 1 : undefined} label={previousLabel} dir="previous">
            <path d="M10 4l-4 4 4 4" />
          </Step>
        </li>
        {range.map((n, i) =>
          n === "gap" ? (
            <li key={`gap-${i}`}>
              <span className="eq-pagination-gap" aria-hidden="true">
                …
              </span>
            </li>
          ) : (
            <li key={n}>
              {buttons ? (
                <button
                  type="button"
                  className="eq-pagination-link"
                  aria-label={`Page ${n}`}
                  aria-current={n === current ? "page" : undefined}
                  {...buttonTo(onPageChange, n)}
                >
                  {n}
                </button>
              ) : (
                <a
                  className="eq-pagination-link"
                  aria-label={`Page ${n}`}
                  aria-current={n === current ? "page" : undefined}
                  {...linkTo(href, onPageChange, n)}
                >
                  {n}
                </a>
              )}
            </li>
          ),
        )}
        <li>
          <Step go={go} to={current < last ? current + 1 : undefined} label={nextLabel} dir="next">
            <path d="M6 4l4 4-4 4" />
          </Step>
        </li>
      </ul>
    </nav>
  );
}

/**
 * Previous or next. With no page to go to, a link stays a link in name but has no href, so it
 * cannot be focused or followed; a button stays focusable with aria-disabled, so focus is not lost
 * when the last page is reached.
 */
function Step({
  go,
  to,
  label,
  dir,
  children,
}: {
  go: Go;
  to: number | undefined;
  label: string;
  dir: "previous" | "next";
  children: ReactNode;
}) {
  const icon = (
    <svg
      viewBox="0 0 16 16"
      fill="none"
      stroke="currentColor"
      strokeWidth="1.5"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      {children}
    </svg>
  );
  const { href, onPageChange } = go;
  if (href === undefined) {
    return (
      <button
        type="button"
        className="eq-pagination-step"
        data-dir={dir}
        aria-disabled={to === undefined || undefined}
        aria-label={label}
        {...(to === undefined ? {} : buttonTo(onPageChange, to))}
      >
        {icon}
      </button>
    );
  }
  return (
    <a
      className="eq-pagination-step"
      data-dir={dir}
      role={to === undefined ? "link" : undefined}
      aria-disabled={to === undefined || undefined}
      aria-label={label}
      rel={to === undefined ? undefined : dir === "previous" ? "prev" : "next"}
      {...(to === undefined ? {} : linkTo(href, onPageChange, to))}
    >
      {icon}
    </a>
  );
}
components/equip/pagination.css89 lines
@layer equip {
  .eq-pagination {
    font: var(--eq-weight) var(--eq-text-s) / 1 var(--eq-font);
  }

  .eq-pagination-list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--eq-space-1);
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .eq-pagination-link,
  .eq-pagination-step {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--eq-h-s);
    height: var(--eq-h-s);
    padding: 0 var(--eq-space-1);
    border-radius: var(--eq-radius-s);
    color: var(--eq-ink);
    font-variant-numeric: tabular-nums;
    text-decoration: none;
    transition:
      background-color var(--eq-dur) var(--eq-ease),
      color var(--eq-dur) var(--eq-ease),
      scale var(--eq-dur) var(--eq-ease);
  }

  /* With onPageChange and no href the pages are buttons: the same look, none of the button chrome. */
  button:is(.eq-pagination-link, .eq-pagination-step) {
    margin: 0;
    border: 0;
    background: transparent;
    font-family: inherit;
    font-size: inherit;
    font-weight: inherit;
    line-height: inherit;
    cursor: pointer;
  }

  .eq-pagination-link:active,
  .eq-pagination-step:is([href], button:not([aria-disabled="true"])):active {
    scale: var(--eq-press);
  }

  .eq-pagination-step > svg {
    width: 16px;
    height: 16px;
  }

  .eq-pagination-link[aria-current="page"] {
    background: var(--eq-solid);
    color: var(--eq-solid-ink);
    font-weight: var(--eq-weight-strong);
  }

  @media (hover: hover) {
    .eq-pagination-link:not([aria-current="page"]):hover,
    .eq-pagination-step:is([href], button:not([aria-disabled="true"])):hover {
      background: var(--eq-tint);
    }
  }

  .eq-pagination-link:focus-visible,
  .eq-pagination-step:focus-visible {
    outline: 2px solid var(--eq-focus);
    outline-offset: 2px;
  }

  .eq-pagination-step[aria-disabled="true"] {
    opacity: 0.45;
    cursor: not-allowed;
  }

  /* Not interactive, so narrower than a page link. */
  .eq-pagination-gap {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: var(--eq-h-s);
    color: var(--eq-ink-3);
  }
}

EqUIp is in beta: free for non-commercial use, no warranty, and things can change. Terms

Nothing picked yet. Press A on a part, or use the plus on the board, and the install command builds here.

Loading the index.

Loading the index