VirtualizedList from @prepared911/ui-core renders only the rows in the viewport to keep long lists performant. The component handles scroll virtualization, item measurement, and loading/error messaging; your job is to keep the list focus-friendly, announce boundary conditions, and ensure essential content never lives in rows that only exist visually.
What the component handles
- Virtualization. Off-screen rows are unmounted but remain addressable via the scroll index.
- Scroll position management. Sticky headers, grouping, and scroll-to-index work with assistive tech when focus follows the index.
- Loading and error messaging.
loading and error props supply visible feedback that can be announced.
- A real list role. Render each item as a
<li> inside <ul> or apply role="list" / role="listitem" to the scroll container so screen readers understand the structure.
- Keyboard navigation. For selectable lists, implement arrow-key navigation with roving
tabIndex or aria-activedescendant; for read-only lists, standard scroll plus Tab is fine.
- Live announcements at the boundaries. When a new batch loads, announce it via a polite live region; when an error occurs, switch to an assertive role or a prominent error callout.
- Content, not just rows. Each row's primary text must remain in the DOM when the row is focused; don't stash critical info in elements that virtualize away on blur.
- Tab moves focus to the list.
- Arrow Up / Down navigate rows when a roving focus pattern is in place.
- Home / End jump to the first / last row.
- Page Up / Page Down page through the list.
role="list" (or native <ul>) on the container.
role="listitem" (or native <li>) on each row.
aria-activedescendant when selection is controlled without shifting DOM focus between rows.
Screen reader announcements
- Infinite-loading lists should announce "Loading more" via
aria-live="polite" and, on completion, announce the count added.
- Error states should announce once (not every render) and provide a retry path.
- Broken tab sequences. If focused rows unmount during scroll, focus can be lost. Keep focus anchored to a persistent container (e.g.
tabindex="0" on the scroll region) and use aria-activedescendant.
- Screen reader mismatch. Some screen readers ignore virtualized content that isn't in the DOM. Counters ("1 of 500") and accurate live announcements bridge the gap.