{pager 'products'} writes the page links for a tag that the page handling option slices - {products page=$pg ?? 1, rows=12}. While that tag renders, its paging books the current page, the rows per page and the number of rows it pages through (after the options written before page, such as a where); the pager reads that booking. So the paged tag must come first: under the strict check a pager without a paged tag of that name before it is an error.

Written alone the tag answers <nav class="pager" aria-label="Pagination"> with links: previous, the first page, a window of pages around the current one, the last page, next. The current page is marked aria-current="page", a gap of pages left out is …, and a previous or next that does not exist is a disabled <span>. The labels pager.label, pager.previous and pager.next come from the application's _lang/ catalog when it has them. When everything fits on one page the pager answers nothing.

Each link is $padGo with the page the request asked for, the request's other query values kept and the page value set - so a search or filter in the address survives. The value is the variable the page option was written with (page=$pg gives &pg=3), or query=.

Written as a pair the pager hands the links over as rows for markup of your own: kind (prev, next, page, current, gap), page, label and href - empty for a gap and for a missing previous or next. A single page gives no rows, so the pair shows its @else@ branch. A table of the Select subsystem is paged by its SQL limit; the pager then counts the same query to know the total.