Virtualized Data Grid
Tier 2: Light-DOMEnterprise data grid capable of virtualizing and streaming >10,000 to 100,000 rows at 60 FPS
Installation
$npx nnui add data-grid
copy$npm install nnui-kit
copyInteractive Preview & Code
/
Triple-Sync Engine Active:
persist="url" (URL query sync) + sync="cross-tab" (BroadcastChannel)
<div class="demo-grid-wrapper" style="border: none; border-radius: 0; background: var(--ui-bg-surface); overflow: hidden;">
<!-- Grid Controls Toolbar -->
<div data-ui="stack" data-direction="row" data-justify="between" data-align="center" style="padding: 0.85rem 1.25rem; border-bottom: 1px solid var(--ui-border-subtle); background: var(--ui-bg-subtle); flex-wrap: wrap; gap: 0.75rem;">
<div style="position: relative; flex: 1; max-width: 360px; min-width: 240px;">
<svg style="position: absolute; left: 10px; top: 50%; transform: translateY(-50%); width: 14px; height: 14px; color: var(--ui-text-muted); pointer-events: none;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<input data-ui="input" id="grid-search-input" placeholder="Search 10,000 records (e.g. BTC, Executed)..." style="padding-left: 2rem; padding-right: 2rem; font-size: 0.85rem; width: 100%; height: 36px; border-radius: var(--ui-radius-md); background: var(--ui-bg-surface); border: 1px solid var(--ui-border-default);">
<kbd style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); font-size: 0.65rem; font-family: var(--ui-font-mono, monospace); padding: 0.1rem 0.35rem; border-radius: 4px; background: var(--ui-bg-subtle); color: var(--ui-text-muted); border: 1px solid var(--ui-border-subtle); pointer-events: none;">/</kbd>
</div>
<div data-ui="stack" data-direction="row" data-gap="2" data-align="center" style="flex-wrap: wrap;">
<button class="ui-button ui-button--secondary" id="toggle-stream-btn" style="font-size: 0.8rem; height: 36px; padding: 0 0.85rem; display: inline-flex; align-items: center; gap: 0.45rem;">
<span class="stream-dot" style="width: 7px; height: 7px; border-radius: 50%; background: var(--ui-color-success-500); display: inline-block;"></span>
<span id="stream-btn-text">Start Live Stream</span>
</button>
<button class="ui-button ui-button--secondary" id="add-batch-btn" style="font-size: 0.8rem; height: 36px; padding: 0 0.85rem; display: inline-flex; align-items: center; gap: 0.4rem;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
<span>Add 5,000 Rows</span>
</button>
<button class="ui-button ui-button--secondary" id="export-csv-btn" style="font-size: 0.8rem; height: 36px; padding: 0 0.85rem; display: inline-flex; align-items: center; gap: 0.4rem;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
<span>Export CSV</span>
</button>
</div>
</div>
<!-- Platform Innovations Live Banner -->
<div style="background: rgba(99, 102, 241, 0.08); border-bottom: 1px solid rgba(99, 102, 241, 0.2); padding: 0.5rem 1.25rem; display: flex; align-items: center; justify-content: space-between; font-size: 0.8rem; flex-wrap: wrap; gap: 0.5rem;">
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span style="display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--ui-color-brand-500);"></span>
<span style="color: var(--ui-text-primary); font-weight: 600;">Triple-Sync Engine Active:</span>
<span style="color: var(--ui-text-secondary);"><code style="color: var(--ui-color-brand-400);">persist="url"</code> (URL query sync) + <code style="color: var(--ui-color-brand-400);">sync="cross-tab"</code> (BroadcastChannel)</span>
</div>
<button type="button" class="ui-button ui-button--secondary" onclick="window.nnuiInspect(document.getElementById('demo-data-grid'))" style="padding: 0.2rem 0.6rem; font-size: 0.75rem; height: 26px;">
🤖 Inspect AI Schema
</button>
</div>
<!-- Component Render -->
<ui-data-grid id="demo-data-grid" data-persist-id="grid" row-height="40" overscan="6" persist="url" sync="cross-tab" style="border: none; border-radius: 0; box-shadow: none;">
<div data-part="viewport" class="grid-viewport" style="height: 480px; overflow-y: auto;">
<div data-part="runway" class="grid-runway">
<table data-part="grid-table" class="ui-table grid-table">
<thead></thead>
<tbody data-part="virtual-tbody" class="grid-tbody"></tbody>
</table>
</div>
</div>
<div data-part="footer" class="grid-footer"></div>
</ui-data-grid>
</div>import React, { useState } from 'react';
import 'nnui-kit'; // Registers universal Light-DOM elements
export default function VirtualizeddatagridDemo() {
const [tab, setTab] = useState('all');
const [val, setVal] = useState(50);
const [selected, setSelected] = useState('');
const [query, setQuery] = useState('');
return (
<div className="demo-grid-wrapper" style="border: none; border-radius: 0; background: var(--ui-bg-surface); overflow: hidden;">
<!-- Grid Controls Toolbar -->
<div data-ui="stack" data-direction="row" data-justify="between" data-align="center" style="padding: 0.85rem 1.25rem; border-bottom: 1px solid var(--ui-border-subtle); background: var(--ui-bg-subtle); flex-wrap: wrap; gap: 0.75rem;">
<div style="position: relative; flex: 1; max-width: 360px; min-width: 240px;">
<svg style="position: absolute; left: 10px; top: 50%; transform: translateY(-50%); width: 14px; height: 14px; color: var(--ui-text-muted); pointer-events: none;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<input data-ui="input" id="grid-search-input" placeholder="Search 10,000 records (e.g. BTC, Executed)..." style="padding-left: 2rem; padding-right: 2rem; font-size: 0.85rem; width: 100%; height: 36px; border-radius: var(--ui-radius-md); background: var(--ui-bg-surface); border: 1px solid var(--ui-border-default);" />
<kbd style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); font-size: 0.65rem; font-family: var(--ui-font-mono, monospace); padding: 0.1rem 0.35rem; border-radius: 4px; background: var(--ui-bg-subtle); color: var(--ui-text-muted); border: 1px solid var(--ui-border-subtle); pointer-events: none;">/</kbd>
</div>
<div data-ui="stack" data-direction="row" data-gap="2" data-align="center" style="flex-wrap: wrap;">
<button className="ui-button ui-button--secondary" id="toggle-stream-btn" style="font-size: 0.8rem; height: 36px; padding: 0 0.85rem; display: inline-flex; align-items: center; gap: 0.45rem;">
<span className="stream-dot" style="width: 7px; height: 7px; border-radius: 50%; background: var(--ui-color-success-500); display: inline-block;"></span>
<span id="stream-btn-text">Start Live Stream</span>
</button>
<button className="ui-button ui-button--secondary" id="add-batch-btn" style="font-size: 0.8rem; height: 36px; padding: 0 0.85rem; display: inline-flex; align-items: center; gap: 0.4rem;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
<span>Add 5,000 Rows</span>
</button>
<button className="ui-button ui-button--secondary" id="export-csv-btn" style="font-size: 0.8rem; height: 36px; padding: 0 0.85rem; display: inline-flex; align-items: center; gap: 0.4rem;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
<span>Export CSV</span>
</button>
</div>
</div>
<!-- Platform Innovations Live Banner -->
<div style="background: rgba(99, 102, 241, 0.08); border-bottom: 1px solid rgba(99, 102, 241, 0.2); padding: 0.5rem 1.25rem; display: flex; align-items: center; justify-content: space-between; font-size: 0.8rem; flex-wrap: wrap; gap: 0.5rem;">
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span style="display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--ui-color-brand-500);"></span>
<span style="color: var(--ui-text-primary); font-weight: 600;">Triple-Sync Engine Active:</span>
<span style="color: var(--ui-text-secondary);"><code style="color: var(--ui-color-brand-400);">persist="url"</code> (URL query sync) + <code style="color: var(--ui-color-brand-400);">sync="cross-tab"</code> (BroadcastChannel)</span>
</div>
<button type="button" className="ui-button ui-button--secondary" onclick="window.nnuiInspect(document.getElementById('demo-data-grid'))" style="padding: 0.2rem 0.6rem; font-size: 0.75rem; height: 26px;">
🤖 Inspect AI Schema
</button>
</div>
<!-- Component Render -->
<ui-data-grid id="demo-data-grid" data-persist-id="grid" row-height="40" overscan="6" persist="url" sync="cross-tab" style="border: none; border-radius: 0; box-shadow: none;">
<div data-part="viewport" className="grid-viewport" style="height: 480px; overflow-y: auto;">
<div data-part="runway" className="grid-runway">
<table data-part="grid-table" className="ui-table grid-table">
<thead></thead>
<tbody data-part="virtual-tbody" className="grid-tbody"></tbody>
</table>
</div>
</div>
<div data-part="footer" className="grid-footer"></div>
</ui-data-grid>
</div>
);
}<script setup>
import { ref } from 'vue';
import 'nnui-kit'; // Registers universal Light-DOM elements
const activeTab = ref('all');
const sliderValue = ref(50);
const selected = ref('');
const query = ref('');
const handleSelect = (val) => {
console.log('Selected:', val);
};
</script>
<template>
<div class="demo-grid-wrapper" style="border: none; border-radius: 0; background: var(--ui-bg-surface); overflow: hidden;">
<!-- Grid Controls Toolbar -->
<div data-ui="stack" data-direction="row" data-justify="between" data-align="center" style="padding: 0.85rem 1.25rem; border-bottom: 1px solid var(--ui-border-subtle); background: var(--ui-bg-subtle); flex-wrap: wrap; gap: 0.75rem;">
<div style="position: relative; flex: 1; max-width: 360px; min-width: 240px;">
<svg style="position: absolute; left: 10px; top: 50%; transform: translateY(-50%); width: 14px; height: 14px; color: var(--ui-text-muted); pointer-events: none;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<input data-ui="input" id="grid-search-input" placeholder="Search 10,000 records (e.g. BTC, Executed)..." style="padding-left: 2rem; padding-right: 2rem; font-size: 0.85rem; width: 100%; height: 36px; border-radius: var(--ui-radius-md); background: var(--ui-bg-surface); border: 1px solid var(--ui-border-default);">
<kbd style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); font-size: 0.65rem; font-family: var(--ui-font-mono, monospace); padding: 0.1rem 0.35rem; border-radius: 4px; background: var(--ui-bg-subtle); color: var(--ui-text-muted); border: 1px solid var(--ui-border-subtle); pointer-events: none;">/</kbd>
</div>
<div data-ui="stack" data-direction="row" data-gap="2" data-align="center" style="flex-wrap: wrap;">
<button class="ui-button ui-button--secondary" id="toggle-stream-btn" style="font-size: 0.8rem; height: 36px; padding: 0 0.85rem; display: inline-flex; align-items: center; gap: 0.45rem;">
<span class="stream-dot" style="width: 7px; height: 7px; border-radius: 50%; background: var(--ui-color-success-500); display: inline-block;"></span>
<span id="stream-btn-text">Start Live Stream</span>
</button>
<button class="ui-button ui-button--secondary" id="add-batch-btn" style="font-size: 0.8rem; height: 36px; padding: 0 0.85rem; display: inline-flex; align-items: center; gap: 0.4rem;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
<span>Add 5,000 Rows</span>
</button>
<button class="ui-button ui-button--secondary" id="export-csv-btn" style="font-size: 0.8rem; height: 36px; padding: 0 0.85rem; display: inline-flex; align-items: center; gap: 0.4rem;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
<span>Export CSV</span>
</button>
</div>
</div>
<!-- Platform Innovations Live Banner -->
<div style="background: rgba(99, 102, 241, 0.08); border-bottom: 1px solid rgba(99, 102, 241, 0.2); padding: 0.5rem 1.25rem; display: flex; align-items: center; justify-content: space-between; font-size: 0.8rem; flex-wrap: wrap; gap: 0.5rem;">
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span style="display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--ui-color-brand-500);"></span>
<span style="color: var(--ui-text-primary); font-weight: 600;">Triple-Sync Engine Active:</span>
<span style="color: var(--ui-text-secondary);"><code style="color: var(--ui-color-brand-400);">persist="url"</code> (URL query sync) + <code style="color: var(--ui-color-brand-400);">sync="cross-tab"</code> (BroadcastChannel)</span>
</div>
<button type="button" class="ui-button ui-button--secondary" onclick="window.nnuiInspect(document.getElementById('demo-data-grid'))" style="padding: 0.2rem 0.6rem; font-size: 0.75rem; height: 26px;">
🤖 Inspect AI Schema
</button>
</div>
<!-- Component Render -->
<ui-data-grid id="demo-data-grid" data-persist-id="grid" row-height="40" overscan="6" persist="url" sync="cross-tab" style="border: none; border-radius: 0; box-shadow: none;">
<div data-part="viewport" class="grid-viewport" style="height: 480px; overflow-y: auto;">
<div data-part="runway" class="grid-runway">
<table data-part="grid-table" class="ui-table grid-table">
<thead></thead>
<tbody data-part="virtual-tbody" class="grid-tbody"></tbody>
</table>
</div>
</div>
<div data-part="footer" class="grid-footer"></div>
</ui-data-grid>
</div>
</template><script>
import 'nnui-kit';
let activeTab = $state('all');
let sliderValue = $state(50);
let selected = $state('');
</script>
<div class="demo-grid-wrapper" style="border: none; border-radius: 0; background: var(--ui-bg-surface); overflow: hidden;">
<!-- Grid Controls Toolbar -->
<div data-ui="stack" data-direction="row" data-justify="between" data-align="center" style="padding: 0.85rem 1.25rem; border-bottom: 1px solid var(--ui-border-subtle); background: var(--ui-bg-subtle); flex-wrap: wrap; gap: 0.75rem;">
<div style="position: relative; flex: 1; max-width: 360px; min-width: 240px;">
<svg style="position: absolute; left: 10px; top: 50%; transform: translateY(-50%); width: 14px; height: 14px; color: var(--ui-text-muted); pointer-events: none;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<input data-ui="input" id="grid-search-input" placeholder="Search 10,000 records (e.g. BTC, Executed)..." style="padding-left: 2rem; padding-right: 2rem; font-size: 0.85rem; width: 100%; height: 36px; border-radius: var(--ui-radius-md); background: var(--ui-bg-surface); border: 1px solid var(--ui-border-default);">
<kbd style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); font-size: 0.65rem; font-family: var(--ui-font-mono, monospace); padding: 0.1rem 0.35rem; border-radius: 4px; background: var(--ui-bg-subtle); color: var(--ui-text-muted); border: 1px solid var(--ui-border-subtle); pointer-events: none;">/</kbd>
</div>
<div data-ui="stack" data-direction="row" data-gap="2" data-align="center" style="flex-wrap: wrap;">
<button class="ui-button ui-button--secondary" id="toggle-stream-btn" style="font-size: 0.8rem; height: 36px; padding: 0 0.85rem; display: inline-flex; align-items: center; gap: 0.45rem;">
<span class="stream-dot" style="width: 7px; height: 7px; border-radius: 50%; background: var(--ui-color-success-500); display: inline-block;"></span>
<span id="stream-btn-text">Start Live Stream</span>
</button>
<button class="ui-button ui-button--secondary" id="add-batch-btn" style="font-size: 0.8rem; height: 36px; padding: 0 0.85rem; display: inline-flex; align-items: center; gap: 0.4rem;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
<span>Add 5,000 Rows</span>
</button>
<button class="ui-button ui-button--secondary" id="export-csv-btn" style="font-size: 0.8rem; height: 36px; padding: 0 0.85rem; display: inline-flex; align-items: center; gap: 0.4rem;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
<span>Export CSV</span>
</button>
</div>
</div>
<!-- Platform Innovations Live Banner -->
<div style="background: rgba(99, 102, 241, 0.08); border-bottom: 1px solid rgba(99, 102, 241, 0.2); padding: 0.5rem 1.25rem; display: flex; align-items: center; justify-content: space-between; font-size: 0.8rem; flex-wrap: wrap; gap: 0.5rem;">
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span style="display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--ui-color-brand-500);"></span>
<span style="color: var(--ui-text-primary); font-weight: 600;">Triple-Sync Engine Active:</span>
<span style="color: var(--ui-text-secondary);"><code style="color: var(--ui-color-brand-400);">persist="url"</code> (URL query sync) + <code style="color: var(--ui-color-brand-400);">sync="cross-tab"</code> (BroadcastChannel)</span>
</div>
<button type="button" class="ui-button ui-button--secondary" onclick="window.nnuiInspect(document.getElementById('demo-data-grid'))" style="padding: 0.2rem 0.6rem; font-size: 0.75rem; height: 26px;">
🤖 Inspect AI Schema
</button>
</div>
<!-- Component Render -->
<ui-data-grid id="demo-data-grid" data-persist-id="grid" row-height="40" overscan="6" persist="url" sync="cross-tab" style="border: none; border-radius: 0; box-shadow: none;">
<div data-part="viewport" class="grid-viewport" style="height: 480px; overflow-y: auto;">
<div data-part="runway" class="grid-runway">
<table data-part="grid-table" class="ui-table grid-table">
<thead></thead>
<tbody data-part="virtual-tbody" class="grid-tbody"></tbody>
</table>
</div>
</div>
<div data-part="footer" class="grid-footer"></div>
</ui-data-grid>
</div>How to Utilize
NNUI-KIT components live directly in the document Light DOM. You can use them via declarative HTML, with our first-class React wrapper, or platform-natively in Svelte, Vue, Astro, and HTMX:
React 18 & 19 (Next.js 15 App Router)
import 'nnui-kit/css';
import 'nnui-kit/client';
import { VirtualizedDataGrid } from 'nnui-kit/react';Universal Web Components (Vue 3, Svelte 5, Astro, HTMX)
<!-- Automatically registers <ui-data-grid> and <nnui-data-grid> -->
import 'nnui-kit/css';
import 'nnui-kit/client';Platform Features
Zero Shadow DOM
Element lives in the Light DOM. Tailwind and global CSS apply directly.
persist="url | local"
Add declarative persistence attribute to auto-sync state with URL search params.
sync="cross-tab"
Synchronize changes across all open browser tabs via BroadcastChannel.