Numbered Pagination
A numbered page strip that collapses long runs to an ellipsis.
6 个框架中级
An append-on-demand list that announces its new result count.
<!--
The status line is the whole point of this component. Appending rows to a list
is a silent DOM mutation: a sighted user sees the page grow, a screen-reader
user hears nothing at all and has no way to know the button worked. The
aria-live region turns "12 more rows appeared somewhere above you" into a
sentence.
It must be in the DOM on first paint and stay there - a live region injected
at the same moment its text changes is not announced by most screen readers,
which is the single most common way this pattern is got wrong.
-->
<div class="feed">
<ul class="feed__list" id="feed-list">
<li class="feed__item"><span class="feed__title">Quarterly revenue report</span><span class="feed__meta">Finance</span></li>
<li class="feed__item"><span class="feed__title">Onboarding checklist</span><span class="feed__meta">People</span></li>
<li class="feed__item"><span class="feed__title">Incident post-mortem</span><span class="feed__meta">Engineering</span></li>
<li class="feed__item"><span class="feed__title">Brand guidelines</span><span class="feed__meta">Design</span></li>
</ul>
<p class="feed__status" id="feed-status" role="status" aria-live="polite">Showing 4 of 12 results</p>
<nav aria-label="Pagination">
<button class="feed__more" id="feed-more" type="button">Load more</button>
</nav>
</div>
<script>
(function () {
var PAGE_SIZE = 4;
var TOTAL = 12;
var list = document.getElementById('feed-list');
var status = document.getElementById('feed-status');
var button = document.getElementById('feed-more');
var REMAINING = [
{ title: 'Security review', meta: 'Engineering' },
{ title: 'Pricing experiment', meta: 'Growth' },
{ title: 'Support playbook', meta: 'Success' },
{ title: 'Hiring plan', meta: 'People' },
{ title: 'Roadmap Q3', meta: 'Product' },
{ title: 'Vendor audit', meta: 'Finance' },
{ title: 'Accessibility audit', meta: 'Design' },
{ title: 'Data retention policy', meta: 'Legal' }
];
var shown = PAGE_SIZE;
button.addEventListener('click', function () {
// aria-busy + disabled while the request is in flight, so a second click
// cannot queue a duplicate page. Swap the timeout for your fetch.
button.disabled = true;
button.setAttribute('aria-busy', 'true');
button.textContent = 'Loading…';
window.setTimeout(function () {
var next = REMAINING.splice(0, PAGE_SIZE);
next.forEach(function (row) {
var li = document.createElement('li');
li.className = 'feed__item';
var title = document.createElement('span');
title.className = 'feed__title';
title.textContent = row.title;
var meta = document.createElement('span');
meta.className = 'feed__meta';
meta.textContent = row.meta;
li.appendChild(title);
li.appendChild(meta);
list.appendChild(li);
});
shown += next.length;
status.textContent = 'Showing ' + shown + ' of ' + TOTAL + ' results';
if (shown >= TOTAL) {
// Kept in the DOM rather than removed: yanking the element the user
// just activated drops their focus to <body> and loses their place.
button.textContent = 'All results loaded';
button.setAttribute('aria-disabled', 'true');
button.removeAttribute('aria-busy');
return;
}
button.disabled = false;
button.removeAttribute('aria-busy');
button.textContent = 'Load more';
}, 600);
});
})();
</script>| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
items必填 | Result[] | - | 要渲染的项目数组。 |
total必填 | number | - | 所有页面的项目总数。 |
loading | boolean | false | 为 true 时显示加载动画并阻止输入。 |
onLoadMore | () => void | - | 请求下一批内容时调用。 |
ariaLabel | string | 'Pagination' | 按钮的无障碍名称。仅有图标的按钮必须设置。 |
className | string | - | 合并到根元素上的额外类名。 |
The status line is the component, not an accessory to it. Appending rows is a silent DOM mutation: a sighted user watches the list grow, a screen-reader user hears nothing and has no way to tell whether the button did anything at all - so "Showing 12 of 48 results" is the entire feedback loop for them. It is rendered unconditionally rather than only once results arrive, because a live region that mounts at the same moment its text changes is usually not announced; the browser has to have been watching it beforehand. That single detail is the most common way this pattern is got wrong. When the list is exhausted the button stays in the DOM and relabels rather than unmounting - removing the element a user just activated drops their focus to `<body>` and loses their place on the page.