diff --git a/docs/angular/src/content/en/components/for-of.mdx b/docs/angular/src/content/en/components/for-of.mdx index 7c193487df..25596a6946 100644 --- a/docs/angular/src/content/en/components/for-of.mdx +++ b/docs/angular/src/content/en/components/for-of.mdx @@ -7,11 +7,16 @@ llms: description: "The Ignite UI for Angular igxForOf directive is an alternative to ngForOf for templating large amounts of data." --- +import DocsAside from 'igniteui-astro-components/components/mdx/DocsAside.astro'; import Sample from 'igniteui-astro-components/components/mdx/Sample.astro'; import ApiLink from 'igniteui-astro-components/components/mdx/ApiLink.astro'; # Angular Virtual ForOf Directive Overview + +For new lists that virtualize a single axis, consider the [Virtual Scroll](./layouts/virtual-scroll.mdx) component: it measures items at runtime, so items can have different sizes, and it creates its own scroll container. See [How do I replace an igxForOf list with the Virtual Scroll?](./layouts/virtual-scroll.mdx#how-do-i-replace-an-igxforof-list-with-the-virtual-scroll) for the mapping between the two APIs. + +
The Ignite UI for Angular igxForOf directive is an alternative to ngForOf for templating large amounts of data. It uses virtualization behind the scenes to optimize DOM rendering and memory consumption.
diff --git a/docs/angular/src/content/en/components/toc.json b/docs/angular/src/content/en/components/toc.json index a7747226b2..2fbfd77d4e 100644 --- a/docs/angular/src/content/en/components/toc.json +++ b/docs/angular/src/content/en/components/toc.json @@ -2071,6 +2071,11 @@ "name": "Virtual For Directive", "href": "for-of.mdx" }, + { + "name": "Virtual Scroll", + "href": "layouts/virtual-scroll.mdx", + "new": true + }, { "name": "Chip", "href": "chip.mdx", diff --git a/docs/xplat/src/content/en/components/layouts/virtual-scroll.mdx b/docs/xplat/src/content/en/components/layouts/virtual-scroll.mdx new file mode 100644 index 0000000000..7252b7b718 --- /dev/null +++ b/docs/xplat/src/content/en/components/layouts/virtual-scroll.mdx @@ -0,0 +1,709 @@ +--- +title: "Virtual Scroll" +description: "The Virtual Scroll is a component that renders only the items in its viewport plus a small buffer, so large lists scroll smoothly." +keywords: "{Platform} Virtual Scroll, virtualization, virtual list, large lists, infinite scroll, remote data, {ProductName}" +last_updated: "2026-09-17" +license: MIT +mentionedTypes: ["VirtualScroll"] +relatedComponents: ["List", "Card"] +llms: + description: "The {ProductName} Virtual Scroll is a component that renders large lists by keeping only the items in its viewport, plus a configurable buffer, in the DOM." +--- +import DocsAside from 'igniteui-astro-components/components/mdx/DocsAside.astro'; +import PlatformBlock from 'igniteui-astro-components/components/mdx/PlatformBlock.astro'; +import Sample from 'igniteui-astro-components/components/mdx/Sample.astro'; +import ApiLink from 'igniteui-astro-components/components/mdx/ApiLink.astro'; +import Faq from 'igniteui-astro-components/components/mdx/Faq.astro'; +import FaqItem from 'igniteui-astro-components/components/mdx/FaqItem.astro'; + +# Virtual Scroll Component + +The {ProductName} Virtual Scroll is a component that renders large lists by keeping only the items in its viewport, plus a configurable buffer, in the DOM. The scrollbar still spans the whole collection, so a virtual list of a hundred thousand items scrolls like a regular list. + +## Live Demo + + + + + + + + + + + + + +## Anatomy + +The {Platform} Virtual Scroll renders the visible items plus a configurable buffer, and its track preserves the scroll range of the whole collection. + +{/* TODO: add the Virtual Scroll anatomy image and render it with the component. */} + + + +```text +igx-virtual-scroll — scrollable viewport (role="list") +└── .igx-virtual-scroll__track — provides the collection's scroll range + └── .igx-virtual-scroll__content — positions the rendered window + └── .igx-virtual-item — one wrapper per rendered item (data-index); hosts the item template +``` + + + + + +```text +igc-virtual-scroll — scrollable viewport +└── [part="virtualization-track"] — provides the collection's scroll range + └── [part="virtualization-content"] — positions the rendered window + └── div[data-vs-index] — one wrapper per rendered item; hosts the item template +``` + + + +## Getting Started + + + +Set up {ProductName} with the [Getting Started](../general/getting-started.mdx) topic, then import the and the `IgxVirtualItemDirective`, which marks the item template: + +```ts +import { Component } from '@angular/core'; +import { IgxVirtualItemDirective, IgxVirtualScrollComponent } from 'igniteui-angular/virtual-scroll'; + +@Component({ + selector: 'app-employees', + imports: [IgxVirtualScrollComponent, IgxVirtualItemDirective], + templateUrl: './employees.component.html' +}) +export class EmployeesComponent { + public items = Array.from({ length: 100_000 }, (_, i) => ({ name: `Item ${i}` })); +} +``` + +```html + + +
{{ index }}: {{ item.name }}
+
+
+``` + +
+ + + +Set up {ProductName} with the [Getting Started](../general-getting-started.mdx) topic, then register the and give it an item template and data: + +```ts +import { defineComponents, IgcVirtualScrollComponent } from 'igniteui-webcomponents'; +import type { VirtualScrollItemContext } from 'igniteui-webcomponents'; +import { html } from 'lit'; + +defineComponents(IgcVirtualScrollComponent); + +const virtualScroll = document.querySelector('igc-virtual-scroll') as IgcVirtualScrollComponent; +virtualScroll.itemTemplate = (ctx: VirtualScrollItemContext) => + html`
${ctx.index}: ${ctx.value.name}
`; +virtualScroll.data = Array.from({ length: 100_000 }, (_, i) => ({ name: `Item ${i}` })); +``` + +```html + +``` + +
+ +The Virtual Scroll host needs a fixed height for vertical scrolling or a fixed width for horizontal scrolling. A host that grows with its content has no viewport to fill. + +### Prerequisites and Version Compatibility + + + +| Requirement | Value | +| --- | --- | +| Package | `igniteui-angular` (MIT) | +| Entry point | `igniteui-angular/virtual-scroll` | +| First release with the component | ‹VERIFY: first published igniteui-angular version that exports IgxVirtualScrollComponent› | + + + + + +| Requirement | Value | +| --- | --- | +| Package | `igniteui-webcomponents` (MIT) | +| First release with the component | 7.3.0 | +| Item templates | Lit's `html` tag. `lit` is a dependency of `igniteui-webcomponents`; add it to your own dependencies when you import it directly. | + + + +## Usage + +### Item Template + +The Virtual Scroll item template receives the item and its position in the whole collection. Use the index and the total count for position-dependent content, such as alternating styles or `aria-posinset` and `aria-setsize`. + + + +Mark an `ng-template` with `igxVirtualItem`, or pass a template defined elsewhere through , which takes precedence. The template context provides `$implicit` (the item), `index`, `count`, `first`, `last`, `even`, and `odd`. + +```html + + + + + + {{ employee.name }} + {{ employee.email }} + + + + +``` + + + + + +Set to a function that returns a Lit template. The function receives a `VirtualScrollItemContext` with `value` (the item), `index`, `count`, `isFirst`, and `isLast`. Without an item template, the component renders nothing. + +```ts +virtualScroll.itemTemplate = (ctx: VirtualScrollItemContext) => html` + + + ${ctx.value.name} + ${ctx.value.email} + +`; +``` + + + +### Data + +The Virtual Scroll collection is compared by reference. Assign a new array to update the list; changing the bound array in place, for example with `push`, does not update it. + + + +```ts +this.employees = [...this.employees, newEmployee]; +``` + + + + + +```ts +virtualScroll.data = [...virtualScroll.data, newEmployee]; +``` + + + +When `data` changes, the component keeps the measured sizes of the items before the first changed index and measures the rest again when they render. Appending keeps every existing measurement; replacing, filtering, or sorting discards the measurements from the first changed item onwards. + +### Estimated Item Size + +The Virtual Scroll is the size in pixels an item has until it renders and is measured (`50` by default). Items can have different sizes: each measured size replaces the estimate. Set the estimate close to the average item size to keep the scrollbar and `scrollToIndex` accurate before items are measured. + + + +```html +... +``` + + + + + + + +```html + +``` + + + + + +Items are measured by their border box, so margins are not part of an item's size. Space items with padding, or with a `gap` inside the item, instead of margins. + +### Orientation + +The Virtual Scroll sets the scroll axis: `vertical` (default) or `horizontal`. In a horizontal list, give each item a width and the host a height. In a right-to-left context, horizontal scrolling and item positioning are mirrored. + + + +```html + + +
...
+
+
+``` + + + +
+ + + +```html + +``` + + + + + +### Over-Scan + +The Virtual Scroll is the number of extra items rendered beyond each edge of the viewport (`2` by default). A larger value reduces blank areas during fast scrolling and renders more elements. + + + +```html +... +``` + + + + + +```html + +``` + + + +### Scroll to Index + +The Virtual Scroll method scrolls an item into view. Its options are those of the native `scrollIntoView`: `block` (`start`, `center`, `end`, or `nearest`), `inline` for a horizontal list, and `behavior` (`auto` or `smooth`). Items that have not rendered only have an estimated size, so the component measures the items where it lands and corrects the position; the returned promise resolves on the final position. + + + +```ts +private readonly virtualScroll = viewChild.required(IgxVirtualScrollComponent); + +public async goTo(index: number): Promise { + await this.virtualScroll().scrollToIndex(index, { block: 'center' }); +} +``` + + + + + + + +```ts +await virtualScroll.scrollToIndex(index, { block: 'center' }); +``` + + + + + +With `block: 'nearest'`, the position does not change when the item is already fully visible. Indices outside the collection are clamped to the first or last item. + +### Infinite Scroll + + + +The Virtual Scroll output supports append-only loading from remote data. It is emitted when the rendered window nears the end of `data`, and on the first render when the loaded items do not fill the viewport. Append the requested items as a new array: + +```html + + ... + +``` + +```ts +public readonly employees = signal(firstPage); + +public loadMore(request: VirtualScrollDataRequest): void { + this.service.fetch(request.startIndex, request.count).subscribe(page => { + this.employees.update(current => [...current, ...page]); + }); +} +``` + + + + + + + +The Virtual Scroll `igcDataRequest` event supports append-only loading from remote data. It is emitted when the rendered window nears the end of `data`, and on the first render when the loaded items do not fill the viewport. Append the requested items as a new array: + +```ts +virtualScroll.addEventListener('igcDataRequest', (event: CustomEvent) => { + const { startIndex, count } = event.detail; + fetchEmployees(startIndex, count).then(page => { + virtualScroll.data = [...virtualScroll.data, ...page]; + }); +}); +``` + + + + + +Only one data request is pending at a time; the next one follows the next `data` change. An empty `data` emits no request, so load the first page yourself. When the source has no more items, stop appending: the component does not request the same start index again. + + + +### Paged Data + +The Angular Virtual Scroll input binds a page of a larger collection instead of `data`. The list is as long as `totalCount`, so the scrollbar spans the whole collection while only the page is in memory, and indices that the page does not cover render nothing. + +```ts +interface VirtualDataWindow { + readonly items: readonly T[]; // the loaded page + readonly startIndex: number; // the index of items[0] in the whole collection + readonly totalCount: number; // the size of the whole collection +} +``` + +Load the next page from the range that reports. Cancel the previous request, so a slow response cannot replace a newer page: + +```html + + ... + +``` + +```ts +public readonly page = signal>({ items: [], startIndex: 0, totalCount: 100_000 }); +private pending?: Subscription; + +public onStateChange(state: VirtualScrollState): void { + const page = this.page(); + if (state.startIndex >= page.startIndex && state.endIndex < page.startIndex + page.items.length) { + return; // The loaded page already covers the range. + } + + const startIndex = Math.max(0, state.startIndex - 30); + const count = state.endIndex + 30 - startIndex + 1; + + this.pending?.unsubscribe(); + this.pending = this.service.fetch(startIndex, count).subscribe(result => { + this.page.set({ items: result.items, startIndex, totalCount: result.total }); + }); +} +``` + + + +Measured sizes are kept per index while `totalCount` stays the same; a page with a different `totalCount`, such as a filtered result, is measured again. `dataRequest` is not emitted while `dataWindow` is bound. The component stores one size entry per index, so its memory grows with `totalCount`: roughly 17 MB for a million items. + + + +### Layout Complete + +The Virtual Scroll property is a promise that resolves when the current render, the measurements it triggers, and the renders they schedule are complete. Await it before you read rendered items after a `data` change, a scroll, or a resize. + + + +```ts +this.employees = await firstValueFrom(this.service.fetchAll()); +await this.virtualScroll().layoutComplete; +``` + + + + + +```ts +virtualScroll.data = await fetchEmployees(); +await virtualScroll.layoutComplete; +``` + + + +### Do/Don't + +{/* TODO: add the Virtual Scroll Do/Don't guidance image from Indigo.Design when it is available. */} + + + +**When to use:** Use the Virtual Scroll for a long list that is too large to render at once, such as a directory, a feed, a log, or a strip of cards, including lists that load remote data while scrolling. + +**When not to use:** Render a short list directly with the [List](../list.mdx) and `@for`. Use the [Grid](../grid/grid.mdx) for tabular data with columns, sorting, or filtering. Show a small set of rich items as [Card](../card.mdx) elements without virtualization. + + + + + +**When to use:** Use the Virtual Scroll for a long list that is too large to render at once, such as a directory, a feed, a log, or a strip of cards, including lists that load remote data while scrolling. + +**When not to use:** Render a short list directly with the [List](../grids/list.mdx). Show a small set of rich items as [Card](./card.mdx) elements without virtualization. + + + +| Do | Don't | +| --- | --- | +| Give the host a fixed height (vertical) or width (horizontal). | Let the host grow with its content. | +| Set `estimatedItemSize` close to the average item size. | Keep the 50px default for much larger or smaller items. | +| Assign a new array when the collection changes. | Change the bound array in place. | +| Space items with padding or `gap`. | Space items with margins. | + +## Properties + + + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| | `T[]` | `[]` | The collection to virtualize. Compared by reference. | +| | `VirtualDataWindow \| null` | `null` | A page of a larger collection, used instead of `data` while it is set. | +| | `'vertical' \| 'horizontal'` | `'vertical'` | The scroll axis. | +| | `number` | `2` | Extra items rendered beyond each edge of the viewport. | +| | `number` | `50` | The size in pixels of an item until it is measured. A non-positive value uses `50`. | +| | `TemplateRef> \| null` | `null` | The item template. Takes precedence over a projected `ng-template[igxVirtualItem]`. | +| | `Promise` (read-only) | — | Resolves when rendering and item measurement have settled. | + + + + + +| Name | Type | Default | Description | +| --- | --- | --- | --- | +| | `T[]` | `[]` | The collection to virtualize. Compared by reference. Property only. | +| | `'vertical' \| 'horizontal'` | `'vertical'` | The scroll axis. Attribute: `orientation`. | +| | `number` | `2` | Extra items rendered beyond each edge of the viewport. Attribute: `over-scan`. | +| | `number` | `50` | The size in pixels of an item until it is measured. A non-positive value uses `50`. Attribute: `estimated-item-size`. | +| | `VirtualScrollItemTemplate \| null` | `null` | The function that renders each item. Property only. | +| | `Promise` (read-only) | — | Resolves when rendering and item measurement have settled. | + + + +## Methods + +| Name | Returns | Description | +| --- | --- | --- | +| | `Promise` | Scrolls the item at `index` into view and resolves when the corrected position is stable. | + +## Events + + + +| Name | Payload | Description | +| --- | --- | --- | +| | `VirtualScrollState` | Emitted when the rendered window changes: `startIndex`, `endIndex`, `viewportSize`, `totalSize`. | +| | `VirtualScrollDataRequest` | Emitted when the rendered window nears the end of `data`: `startIndex`, `count`. Not emitted while `dataWindow` is bound. | + + + + + +| Name | Detail | Description | +| --- | --- | --- | +| `igcStateChange` | `VirtualScrollState` | Emitted when the rendered window changes: `startIndex`, `endIndex`, `viewportSize`, `totalSize`. | +| `igcDataRequest` | `VirtualScrollDataRequest` | Emitted when the rendered window nears the end of `data`: `startIndex`, `count`. | + + + +## Styling + +The {Platform} Virtual Scroll has no theme of its own: it lays out the viewport, and the rendered items take their styles from the elements and components in the item template. + + + +Size the host and target the rendered items with the classes from the [Anatomy](#anatomy): + +```scss +.employees igx-virtual-scroll { + block-size: 480px; +} + +.employees .igx-virtual-item:nth-child(even) { + background: var(--ig-gray-100); +} +``` + + + + + +The component renders into its light DOM, so regular selectors reach the rendered items. Its default styles give the host a height of `18.75rem`; override the height to size the viewport. + +```css +igc-virtual-scroll.employees { + height: 480px; +} + +igc-virtual-scroll.employees [data-vs-index]:nth-child(even) { + background: var(--ig-gray-100); +} +``` + + + +## Accessibility + +The {Platform} Virtual Scroll keeps only the rendered window in the DOM, so the item template has to expose each item's position in the whole collection. + +### Keyboard Interaction + +The {Platform} Virtual Scroll adds no key handlers. The host is a native scroll container, and a focused scroll container scrolls with the browser's keys: + +| Key | Action | +| --- | --- | +| Arrow Up / Arrow Down | Scrolls a vertical list. | +| Arrow Left / Arrow Right | Scrolls a horizontal list. | +| Page Up / Page Down | Scrolls by about one viewport. | +| Home / End | Scrolls to the start or the end of the collection. | + +The host has no `tabindex`. Browsers differ in whether a scroll container without focusable content can receive focus, so set `tabindex="0"` on the host when the items contain nothing focusable. Items scrolled out of view leave the DOM, so focus inside a removed item is lost. + +### Screen Readers / ARIA + + + +- The host has `role="list"`; the track, the content element, and the item wrappers have `role="presentation"`. Items that render `role="listitem"`, such as `igx-list-item`, are exposed as items of that list. +- Map the `index` and `count` template variables to `aria-posinset` and `aria-setsize`. +- Give a focusable host an accessible name with `aria-label` or `aria-labelledby`. + + + + + +- The track, the content element, and the item wrappers have `role="presentation"`. The host has no role: place it inside an element with a list role, such as `igc-list`, or give it `role="list"` when the items render `role="listitem"`. +- Map the `index` and `count` context properties to `aria-posinset` and `aria-setsize`. +- Give a focusable host a role that supports an accessible name, such as `role="list"`, and name it with `aria-label` or `aria-labelledby`. + + + +### Accessibility Compliance + +Infragistics documents the accessibility standards that {ProductName} targets in the [Accessibility Compliance](../interactivity/accessibility-compliance.mdx) topic. This topic makes no conformance claim for the Virtual Scroll: the table lists what the component provides, and the list after it covers what the application must add. + +| Criterion | How the component supports the requirement | +| --- | --- | +| [1.3.1 Info and Relationships](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships) | The wrappers are presentational, so the list structure comes from the host and the item template, which can expose each item's position with `aria-posinset` and `aria-setsize`. | +| [2.1.1 Keyboard](https://www.w3.org/WAI/WCAG21/Understanding/keyboard) | The host is a native scroll container that scrolls with the keyboard once it has focus. Reaching it with the keyboard depends on the application; see the list below. | + +Your responsibilities: + +- Make the host keyboard-reachable with `tabindex="0"` when the items contain nothing focusable, and give it an accessible name. +- Expose the item position with `aria-posinset` and `aria-setsize` from the item template. +- Provide list semantics that fit the item template (see [Screen Readers / ARIA](#screen-readers--aria)). +- Keep application state, such as a selection, in the data rather than in the rendered item elements. + +## Troubleshooting + +### Why does the Virtual Scroll render no items? + +The host has no size on the scroll axis, the item template is missing, or `data` is empty. Give the host a fixed height (vertical) or width (horizontal), set the item template, and check the bound collection. + +### Why does the list not update when I add an item? + +The Virtual Scroll compares `data` by reference, so a change in place is not detected. Assign a new array, for example `[...items, newItem]`. + +### Why does the scrollbar change size while I scroll? + +Items that have not rendered use `estimatedItemSize`, and the total size is corrected as items are measured. Set `estimatedItemSize` close to the average item size. + +### Why do items drift out of place further down the list? + +Margins are not part of an item's measured size. Replace item margins with padding, or with a `gap` inside the item. + + + +### Why does a list inside a drop-down or dialog show its items one frame late? + +A container that is hidden until it opens has no size in the change detection pass that reveals it, so the host is measured after that render and the items render in the next frame. Read the rendered items after `layoutComplete` resolves. + +### How do I replace an igxForOf list with the Virtual Scroll? + +The Virtual Scroll measures items at runtime and creates its own scroll container, so the container size and scroll container inputs of `igxForOf` have no equivalent. The `igxForOf` directive remains available; use the Virtual Scroll for new lists that virtualize a single axis. + +| igxForOf | Virtual Scroll | +| --- | --- | +| `*igxFor="let item of data"` | `[data]="data"` with an `ng-template igxVirtualItem` | +| `igxForScrollOrientation` | `orientation` | +| `igxForContainerSize` | The host's height or width, set with CSS | +| `igxForItemSize` | `estimatedItemSize` (a starting estimate; items are measured) | +| `igxForScrollContainer` | Not needed: the host is the scroll container | +| `scrollTo(index)` | `scrollToIndex(index, options)`, which returns a promise | +| `chunkLoad`, `chunkPreload` | `stateChange` | +| `igxForRemote` with `totalItemCount` | `dataWindow` with `totalCount`, or `data` with `dataRequest` for append-only loading | +| `index`, `count`, `first`, `last`, `even`, `odd` | The same template variables | + +The grids keep their own row and column virtualization; see [Grid Virtualization](../grid/virtualization.mdx). + + + +## Known Limitations + +- The {Platform} Virtual Scroll virtualizes one axis. Rows and columns that are both virtualized require a grid. +- Items scrolled out of view are removed from the DOM together with their focus and internal state. + + + +- With `dataWindow`, a page that keeps `totalCount` but places different records at the same indices keeps the measured sizes of the previous records until those rows render again. + + + +## API References + +- + +## Dependencies + + + +The Angular Virtual Scroll has no dependencies on other components. Import `IgxVirtualScrollComponent` and `IgxVirtualItemDirective` from `igniteui-angular/virtual-scroll`; the structural styles ship with the component. + + + + + +The {Platform} Virtual Scroll has no dependencies on other components and needs no theme of its own. It uses `lit` to render item templates, and the components in the item template need a theme stylesheet. + + + +## Additional Resources + +- [{ProductName} **Forums**]({ForumsLink}) +- [{ProductName} **GitHub**]({GithubLink}) + +## Related Components + + + +- [List](../list.mdx) - Use the List for a short list, or as the container of a virtualized list. +- [Card](../card.mdx) - Use cards for a small set of rich items, or as items of a horizontal Virtual Scroll. +- [Virtual ForOf Directive](../for-of.mdx) - The directive-based virtualization used by existing lists. + + + + + +- [List](../grids/list.mdx) - Use the List for a short list, or as the container of a virtualized list. +- [Card](./card.mdx) - Use cards for a small set of rich items, or as items of a horizontal Virtual Scroll. + + + +## FAQ + + + + The {Platform} Virtual Scroll keeps only the items in its viewport and the over-scan buffer in the DOM, so the size of the collection does not change how many elements render. When the total size of a collection exceeds the browser's scroll limit, the Virtual Scroll maps the collection onto the scroll range the browser supports. + + + Items in the {Platform} Virtual Scroll can have different sizes, because each item is measured once it renders. Set `estimatedItemSize` close to the average item size so that the scrollbar is accurate before items are measured. + + + Call the {Platform} Virtual Scroll `scrollToIndex` method with the item index and optional `block` and `behavior` options. The method returns a promise that resolves when the corrected position is stable. + + + + The Angular Virtual Scroll supports two models. For append-only loading, handle `dataRequest` and assign a new array that includes the requested items. For a collection read a page at a time, bind `dataWindow` and load the range that `stateChange` reports. + + + The {Platform} Virtual Scroll supports append-only loading through the `igcDataRequest` event. Handle the event and assign a new array that includes the requested items to `data`. + + + diff --git a/docs/xplat/src/content/en/toc.json b/docs/xplat/src/content/en/toc.json index 35c627b769..3529988fb5 100644 --- a/docs/xplat/src/content/en/toc.json +++ b/docs/xplat/src/content/en/toc.json @@ -2589,6 +2589,15 @@ "name": "Highlight", "href": "inputs/highlight.mdx" }, + { + "exclude": [ + "React", + "Blazor" + ], + "name": "Virtual Scroll", + "href": "layouts/virtual-scroll.mdx", + "new": true + }, { "name": "Interactions", "header": true